Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fea1a75cdf | |||
| 0dfaeef7bf | |||
| 92a7853258 | |||
| 14f22b4ebf | |||
| 4d972bfab0 | |||
| e005a42a3f | |||
| 58d6d510f9 |
@@ -45,8 +45,8 @@ Use this skill together with the installed `android-emulator-debug` skill. That
|
|||||||
## Build Workflow
|
## Build Workflow
|
||||||
|
|
||||||
1. Verify the JDK/SDK paths match the known working environment.
|
1. Verify the JDK/SDK paths match the known working environment.
|
||||||
2. Build with `make apk`.
|
2. Build with `make apk` for debug validation, or `make apk-release` when validating production signing behavior.
|
||||||
3. If `make apk` fails, inspect the effective `JAVA_HOME`, `ANDROID_SDK_ROOT`, and `ANDROID_NDK_ROOT` before changing code.
|
3. If the build fails, inspect the effective `JAVA_HOME`, `ANDROID_SDK_ROOT`, and `ANDROID_NDK_ROOT` before changing code.
|
||||||
4. If the problem is Android-only, avoid desktop-only conclusions from `go test ./...`.
|
4. If the problem is Android-only, avoid desktop-only conclusions from `go test ./...`.
|
||||||
|
|
||||||
Typical local build:
|
Typical local build:
|
||||||
@@ -55,6 +55,12 @@ Typical local build:
|
|||||||
JAVA_HOME=/usr/lib/jvm/java-25-openjdk make apk
|
JAVA_HOME=/usr/lib/jvm/java-25-openjdk make apk
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Typical local release build:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
JAVA_HOME=/usr/lib/jvm/java-25-openjdk make apk-release
|
||||||
|
```
|
||||||
|
|
||||||
## Emulator Workflow
|
## Emulator Workflow
|
||||||
|
|
||||||
1. Reuse an existing emulator session if one is already running.
|
1. Reuse an existing emulator session if one is already running.
|
||||||
@@ -79,7 +85,7 @@ adb shell dumpsys window | rg 'mCurrentFocus|mFocusedApp'
|
|||||||
|
|
||||||
## Validation Checklist
|
## Validation Checklist
|
||||||
|
|
||||||
- APK builds successfully with `make apk`.
|
- APK builds successfully with the intended target: `make apk` for debug validation or `make apk-release` for release-signing validation.
|
||||||
- App launches to `org.julianfamily.keepassgo/org.gioui.GioActivity`.
|
- App launches to `org.julianfamily.keepassgo/org.gioui.GioActivity`.
|
||||||
- Screenshot shows the expected screen, not just a black frame.
|
- Screenshot shows the expected screen, not just a black frame.
|
||||||
- `logcat` shows no app crash or Android runtime fatal error.
|
- `logcat` shows no app crash or Android runtime fatal error.
|
||||||
|
|||||||
@@ -52,11 +52,17 @@ The installed package version must correspond to the committed source, not a dir
|
|||||||
Use the repo's known-good local JDK unless the environment already proves otherwise:
|
Use the repo's known-good local JDK unless the environment already proves otherwise:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
JAVA_HOME=/usr/lib/jvm/java-25-openjdk make apk
|
JAVA_HOME=/usr/lib/jvm/java-25-openjdk make apk-release
|
||||||
```
|
```
|
||||||
|
|
||||||
If that JDK is unavailable on the current host, use the working replacement already established for the machine and say so in the closeout.
|
If that JDK is unavailable on the current host, use the working replacement already established for the machine and say so in the closeout.
|
||||||
|
|
||||||
|
- `ship it` must use the dedicated release keystore flow, not Gio's implicit debug or temporary signing path.
|
||||||
|
- The default local release-signing paths are:
|
||||||
|
`~/.config/keepassgo/android-release.keystore`
|
||||||
|
`~/.config/keepassgo/android-release.pass`
|
||||||
|
- If those files are unavailable, stop and fix signing instead of shipping a differently signed APK.
|
||||||
|
|
||||||
### 4. Zip The APK
|
### 4. Zip The APK
|
||||||
|
|
||||||
- Create the ZIP under the globally required temporary secret-safe directory.
|
- Create the ZIP under the globally required temporary secret-safe directory.
|
||||||
|
|||||||
@@ -135,11 +135,14 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
signkey_path="$(mktemp)"
|
mkdir -p build/ci-signing
|
||||||
trap 'rm -f -- "$signkey_path"' EXIT
|
signkey_path="$(pwd)/build/ci-signing/android-release.keystore"
|
||||||
|
signpass_path="$(pwd)/build/ci-signing/android-release.pass"
|
||||||
|
trap 'rm -f -- "$signkey_path" "$signpass_path"' EXIT
|
||||||
printf '%s' '${{ secrets.APK_SIGNKEY_B64 }}' | base64 -d > "$signkey_path"
|
printf '%s' '${{ secrets.APK_SIGNKEY_B64 }}' | base64 -d > "$signkey_path"
|
||||||
|
printf '%s' '${{ secrets.APK_SIGNPASS }}' > "$signpass_path"
|
||||||
export APP_VERSION="$(git describe --tags --always --dirty)"
|
export APP_VERSION="$(git describe --tags --always --dirty)"
|
||||||
make apk SIGNKEY="$signkey_path" SIGNPASS='${{ secrets.APK_SIGNPASS }}'
|
make apk-release RELEASE_SIGNKEY="$signkey_path" RELEASE_SIGNPASS_FILE="$signpass_path"
|
||||||
cp build/keepassgo.apk "${DIST_DIR}/keepassgo.apk"
|
cp build/keepassgo.apk "${DIST_DIR}/keepassgo.apk"
|
||||||
|
|
||||||
- name: Upload CI artifacts
|
- name: Upload CI artifacts
|
||||||
|
|||||||
@@ -6,17 +6,42 @@ Build the APK with:
|
|||||||
make apk
|
make apk
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Build the release-signed APK with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make apk-release
|
||||||
|
```
|
||||||
|
|
||||||
|
`make apk` uses a local Java 25 install when `JAVA_HOME` points to one.
|
||||||
|
If the host does not have a working Java 25 install, it falls back to the
|
||||||
|
repo-managed Docker image in `packaging/docker/android-apk/`, which also builds
|
||||||
|
with Java 25.
|
||||||
|
|
||||||
|
`make apk` remains a developer build path and may use Gio's default debug or
|
||||||
|
ephemeral signing behavior if no explicit signing key is provided.
|
||||||
|
`make apk-release` is the production-signing path and fails unless a dedicated
|
||||||
|
release keystore and password file are present.
|
||||||
|
|
||||||
Environment:
|
Environment:
|
||||||
|
|
||||||
- `ANDROID_SDK_ROOT` defaults to `/opt/android-sdk`.
|
- `ANDROID_SDK_ROOT` defaults to `/opt/android-sdk`.
|
||||||
- `ANDROID_NDK_ROOT` defaults to `/opt/android-ndk`.
|
- `ANDROID_NDK_ROOT` defaults to `/opt/android-ndk`.
|
||||||
- `JAVA_HOME` defaults to `/usr/lib/jvm/java-25-openjdk`.
|
- `JAVA_HOME` defaults to `/usr/lib/jvm/java-25-openjdk`.
|
||||||
|
- `APK_BUILD_IMAGE` overrides the Docker image name used by `make apk-container`.
|
||||||
- `APP_ID` overrides the Android application id.
|
- `APP_ID` overrides the Android application id.
|
||||||
- `APP_VERSION` overrides the version shown inside KeePassGO itself.
|
- `APP_VERSION` overrides the version shown inside KeePassGO itself.
|
||||||
- `APK_OUT` overrides the output path.
|
- `APK_OUT` overrides the output path.
|
||||||
- `APK_VERSION` overrides the packaged app version.
|
- `APK_VERSION` overrides the packaged app version.
|
||||||
- `ANDROID_MIN_SDK` overrides the minimum supported Android SDK.
|
- `ANDROID_MIN_SDK` overrides the minimum supported Android SDK.
|
||||||
- `ANDROID_TARGET_SDK` overrides the target Android SDK.
|
- `ANDROID_TARGET_SDK` overrides the target Android SDK.
|
||||||
|
- `SIGNPASS_FILE` provides the signing password by file instead of a command-line argument.
|
||||||
|
- `RELEASE_SIGNKEY` overrides the release keystore path used by `make apk-release`.
|
||||||
|
- `RELEASE_SIGNPASS_FILE` overrides the password file path used by `make apk-release`.
|
||||||
|
|
||||||
|
Default release-signing paths:
|
||||||
|
|
||||||
|
- `~/.config/keepassgo/android-release.keystore`
|
||||||
|
- `~/.config/keepassgo/android-release.pass`
|
||||||
|
|
||||||
Installed machine prerequisites expected by this repo:
|
Installed machine prerequisites expected by this repo:
|
||||||
|
|
||||||
@@ -24,24 +49,28 @@ Installed machine prerequisites expected by this repo:
|
|||||||
- `android-sdk-build-tools`
|
- `android-sdk-build-tools`
|
||||||
- `android-platform-35`
|
- `android-platform-35`
|
||||||
- `android-sdk-platform-tools`
|
- `android-sdk-platform-tools`
|
||||||
- a working JDK install
|
- a working Java 25 JDK install for `make apk-local`, or Docker for `make apk`
|
||||||
|
|
||||||
The repo tracks `gogio` as a Go tool, so the build runs through:
|
The repo tracks `gogio` as a Go tool, and the local build runs through:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
go tool gogio -target android ./cmd/keepassgo ...
|
go tool gogio -target android ./cmd/keepassgo ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The release target wraps `make apk` and injects explicit signing credentials so
|
||||||
|
local release builds and CI use the same stable key without echoing the release
|
||||||
|
password in build logs.
|
||||||
|
|
||||||
The Android build uses the branded icon asset at:
|
The Android build uses the branded icon asset at:
|
||||||
|
|
||||||
- `internal/assets/keepassgo-icon.png`
|
- `internal/assets/keepassgo-icon.png`
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
|
|
||||||
- Gio's Android doc currently references Java 1.8, but the Android build-tools
|
- KeePassGO's documented Android build uses Java 25 locally.
|
||||||
installed on this machine (`d8` from build-tools 37) do not run on Java 8.
|
- If that host setup is unavailable, `make apk` falls back to the Docker image
|
||||||
- In this environment, KeePassGO's APK build requires a newer JDK runtime on
|
so the build still runs under Java 25 instead of encoding a newer host JDK as
|
||||||
`PATH`, which is why the repo defaults `JAVA_HOME` to `/usr/lib/jvm/java-25-openjdk`.
|
a requirement.
|
||||||
- Android runtime testing on the `KeepassGoAPI35` emulator showed a black-screen
|
- Android runtime testing on the `KeepassGoAPI35` emulator showed a black-screen
|
||||||
regression with `gioui.org v0.9.0` while a stock Gio example and KeePassGO both
|
regression with `gioui.org v0.9.0` while a stock Gio example and KeePassGO both
|
||||||
rendered correctly with `gioui.org v0.8.0` on the same emulator and SDK/JDK
|
rendered correctly with `gioui.org v0.8.0` on the same emulator and SDK/JDK
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ ANDROID_SDK_ROOT ?= /opt/android-sdk
|
|||||||
ANDROID_NDK_ROOT ?= /opt/android-ndk
|
ANDROID_NDK_ROOT ?= /opt/android-ndk
|
||||||
JAVA_HOME ?= /usr/lib/jvm/java-25-openjdk
|
JAVA_HOME ?= /usr/lib/jvm/java-25-openjdk
|
||||||
PATH := $(JAVA_HOME)/bin:$(ANDROID_SDK_ROOT)/cmdline-tools/latest/bin:$(ANDROID_SDK_ROOT)/platform-tools:$(PATH)
|
PATH := $(JAVA_HOME)/bin:$(ANDROID_SDK_ROOT)/cmdline-tools/latest/bin:$(ANDROID_SDK_ROOT)/platform-tools:$(PATH)
|
||||||
|
APK_BUILD_IMAGE ?= keepassgo/android-apk-build:java25
|
||||||
APP_ID ?= org.julianfamily.keepassgo
|
APP_ID ?= org.julianfamily.keepassgo
|
||||||
APK_OUT ?= build/keepassgo.apk
|
APK_OUT ?= build/keepassgo.apk
|
||||||
APK_VERSION ?= 0.1.0.1
|
APK_VERSION ?= 0.1.0.1
|
||||||
@@ -11,6 +12,9 @@ ANDROID_MIN_SDK ?= 28
|
|||||||
ANDROID_TARGET_SDK ?= 35
|
ANDROID_TARGET_SDK ?= 35
|
||||||
SIGNKEY ?=
|
SIGNKEY ?=
|
||||||
SIGNPASS ?=
|
SIGNPASS ?=
|
||||||
|
SIGNPASS_FILE ?=
|
||||||
|
RELEASE_SIGNKEY ?= $(HOME)/.config/keepassgo/android-release.keystore
|
||||||
|
RELEASE_SIGNPASS_FILE ?= $(HOME)/.config/keepassgo/android-release.pass
|
||||||
ARCH_PKG_DIR ?= packaging/archlinux/keepassgo-git
|
ARCH_PKG_DIR ?= packaging/archlinux/keepassgo-git
|
||||||
ARCH_PKG_TMPL ?= $(ARCH_PKG_DIR)/PKGBUILD.tmpl
|
ARCH_PKG_TMPL ?= $(ARCH_PKG_DIR)/PKGBUILD.tmpl
|
||||||
ARCH_PKGBUILD ?= $(ARCH_PKG_DIR)/PKGBUILD
|
ARCH_PKGBUILD ?= $(ARCH_PKG_DIR)/PKGBUILD
|
||||||
@@ -25,8 +29,31 @@ ifneq ($(strip $(SIGNPASS)),)
|
|||||||
GOGIO_SIGN_FLAGS += -signpass $(SIGNPASS)
|
GOGIO_SIGN_FLAGS += -signpass $(SIGNPASS)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
.PHONY: apk archlinux-pkgbuild browser-bridge browser-extension-validate
|
CONTAINER_SIGNKEY_MOUNT :=
|
||||||
apk: android/keepassgo-android.jar
|
CONTAINER_SIGNPASSFILE_MOUNT :=
|
||||||
|
CONTAINER_SIGN_ARGS :=
|
||||||
|
ifneq ($(strip $(SIGNKEY)),)
|
||||||
|
CONTAINER_SIGNKEY_MOUNT += -v "$(dir $(abspath $(SIGNKEY))):$(dir $(abspath $(SIGNKEY))):ro"
|
||||||
|
CONTAINER_SIGN_ARGS += SIGNKEY="$(abspath $(SIGNKEY))"
|
||||||
|
endif
|
||||||
|
ifneq ($(strip $(SIGNPASS)),)
|
||||||
|
CONTAINER_SIGN_ARGS += SIGNPASS="$(SIGNPASS)"
|
||||||
|
endif
|
||||||
|
ifneq ($(strip $(SIGNPASS_FILE)),)
|
||||||
|
CONTAINER_SIGNPASSFILE_MOUNT += -v "$(dir $(abspath $(SIGNPASS_FILE))):$(dir $(abspath $(SIGNPASS_FILE))):ro"
|
||||||
|
CONTAINER_SIGN_ARGS += SIGNPASS_FILE="$(abspath $(SIGNPASS_FILE))"
|
||||||
|
endif
|
||||||
|
|
||||||
|
.PHONY: apk apk-local apk-release apk-container apk-container-image archlinux-pkgbuild browser-bridge browser-extension-validate
|
||||||
|
apk:
|
||||||
|
@if [ -x "$(JAVA_HOME)/bin/java" ] && "$(JAVA_HOME)/bin/java" -version 2>&1 | grep -q 'version "25'; then \
|
||||||
|
$(MAKE) apk-local JAVA_HOME="$(JAVA_HOME)"; \
|
||||||
|
else \
|
||||||
|
echo "Using Dockerized Java 25 Android build because JAVA_HOME is not a working Java 25 install."; \
|
||||||
|
$(MAKE) apk-container; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
apk-local: android/keepassgo-android.jar
|
||||||
@test -x "$(JAVA_HOME)/bin/java" || { echo "JAVA_HOME must point to a working JDK install"; exit 1; }
|
@test -x "$(JAVA_HOME)/bin/java" || { echo "JAVA_HOME must point to a working JDK install"; exit 1; }
|
||||||
@test -d "$(ANDROID_SDK_ROOT)" || { echo "ANDROID_SDK_ROOT must point to an Android SDK install"; exit 1; }
|
@test -d "$(ANDROID_SDK_ROOT)" || { echo "ANDROID_SDK_ROOT must point to an Android SDK install"; exit 1; }
|
||||||
@test -d "$(ANDROID_NDK_ROOT)" || { echo "ANDROID_NDK_ROOT must point to an Android NDK install"; exit 1; }
|
@test -d "$(ANDROID_NDK_ROOT)" || { echo "ANDROID_NDK_ROOT must point to an Android NDK install"; exit 1; }
|
||||||
@@ -34,11 +61,17 @@ apk: android/keepassgo-android.jar
|
|||||||
@test -d "$(ANDROID_SDK_ROOT)/platforms/android-$(ANDROID_TARGET_SDK)" || { echo "Android platform android-$(ANDROID_TARGET_SDK) is missing"; exit 1; }
|
@test -d "$(ANDROID_SDK_ROOT)/platforms/android-$(ANDROID_TARGET_SDK)" || { echo "Android platform android-$(ANDROID_TARGET_SDK) is missing"; exit 1; }
|
||||||
@test -d "$(ANDROID_SDK_ROOT)/build-tools" || { echo "Android build-tools are missing"; exit 1; }
|
@test -d "$(ANDROID_SDK_ROOT)/build-tools" || { echo "Android build-tools are missing"; exit 1; }
|
||||||
@mkdir -p "$(dir $(APK_OUT))"
|
@mkdir -p "$(dir $(APK_OUT))"
|
||||||
|
@set -eu; \
|
||||||
|
if [ -n "$(SIGNPASS_FILE)" ]; then \
|
||||||
|
test -f "$(SIGNPASS_FILE)" || { echo "SIGNPASS_FILE does not exist: $(SIGNPASS_FILE)"; exit 1; }; \
|
||||||
|
export GOGIO_SIGNPASS="$$(tr -d '\r\n' < "$(SIGNPASS_FILE)")"; \
|
||||||
|
test -n "$$GOGIO_SIGNPASS" || { echo "SIGNPASS_FILE is empty: $(SIGNPASS_FILE)"; exit 1; }; \
|
||||||
|
fi; \
|
||||||
ANDROID_HOME="$(ANDROID_SDK_ROOT)" \
|
ANDROID_HOME="$(ANDROID_SDK_ROOT)" \
|
||||||
ANDROID_SDK_ROOT="$(ANDROID_SDK_ROOT)" \
|
ANDROID_SDK_ROOT="$(ANDROID_SDK_ROOT)" \
|
||||||
ANDROID_NDK_ROOT="$(ANDROID_NDK_ROOT)" \
|
ANDROID_NDK_ROOT="$(ANDROID_NDK_ROOT)" \
|
||||||
JAVA_HOME="$(JAVA_HOME)" \
|
JAVA_HOME="$(JAVA_HOME)" \
|
||||||
go run ./cmd/build-android-apk -target android \
|
go tool gogio -target android \
|
||||||
-buildmode exe \
|
-buildmode exe \
|
||||||
-appid $(APP_ID) \
|
-appid $(APP_ID) \
|
||||||
-ldflags "$(GO_LDFLAGS)" \
|
-ldflags "$(GO_LDFLAGS)" \
|
||||||
@@ -50,12 +83,39 @@ apk: android/keepassgo-android.jar
|
|||||||
-icon internal/assets/keepassgo-icon.png \
|
-icon internal/assets/keepassgo-icon.png \
|
||||||
./cmd/keepassgo
|
./cmd/keepassgo
|
||||||
|
|
||||||
|
apk-release:
|
||||||
|
@test -f "$(RELEASE_SIGNKEY)" || { echo "Release signing key not found at $(RELEASE_SIGNKEY)"; exit 1; }
|
||||||
|
@test -f "$(RELEASE_SIGNPASS_FILE)" || { echo "Release signing password file not found at $(RELEASE_SIGNPASS_FILE)"; exit 1; }
|
||||||
|
@$(MAKE) apk SIGNKEY="$(abspath $(RELEASE_SIGNKEY))" SIGNPASS_FILE="$(abspath $(RELEASE_SIGNPASS_FILE))"
|
||||||
|
|
||||||
|
apk-container: apk-container-image
|
||||||
|
@command -v docker >/dev/null 2>&1 || { echo "docker is required for apk-container"; exit 1; }
|
||||||
|
@test -d "$(ANDROID_SDK_ROOT)" || { echo "ANDROID_SDK_ROOT must point to an Android SDK install"; exit 1; }
|
||||||
|
@test -d "$(ANDROID_NDK_ROOT)" || { echo "ANDROID_NDK_ROOT must point to an Android NDK install"; exit 1; }
|
||||||
|
docker run --rm \
|
||||||
|
-u "$$(id -u):$$(id -g)" \
|
||||||
|
-v "$(CURDIR):$(CURDIR)" \
|
||||||
|
-w "$(CURDIR)" \
|
||||||
|
-v "$(ANDROID_SDK_ROOT):$(ANDROID_SDK_ROOT)" \
|
||||||
|
-v "$(ANDROID_NDK_ROOT):$(ANDROID_NDK_ROOT)" \
|
||||||
|
$(CONTAINER_SIGNKEY_MOUNT) \
|
||||||
|
$(CONTAINER_SIGNPASSFILE_MOUNT) \
|
||||||
|
-e ANDROID_SDK_ROOT="$(ANDROID_SDK_ROOT)" \
|
||||||
|
-e ANDROID_NDK_ROOT="$(ANDROID_NDK_ROOT)" \
|
||||||
|
-e JAVA_HOME=/opt/java/openjdk \
|
||||||
|
$(APK_BUILD_IMAGE) \
|
||||||
|
make apk-local JAVA_HOME=/opt/java/openjdk $(CONTAINER_SIGN_ARGS)
|
||||||
|
|
||||||
|
apk-container-image:
|
||||||
|
@command -v docker >/dev/null 2>&1 || { echo "docker is required for apk-container-image"; exit 1; }
|
||||||
|
docker build --load -t $(APK_BUILD_IMAGE) packaging/docker/android-apk
|
||||||
|
|
||||||
android/keepassgo-android.jar: $(shell find androidsrc -type f | sort)
|
android/keepassgo-android.jar: $(shell find androidsrc -type f | sort)
|
||||||
@test -x "$(JAVA_HOME)/bin/javac" || { echo "JAVA_HOME must point to a working JDK install"; exit 1; }
|
@test -x "$(JAVA_HOME)/bin/javac" || { echo "JAVA_HOME must point to a working JDK install"; exit 1; }
|
||||||
@test -f "$(ANDROID_SDK_ROOT)/platforms/android-$(ANDROID_TARGET_SDK)/android.jar" || { echo "Android platform android-$(ANDROID_TARGET_SDK) is missing"; exit 1; }
|
@test -f "$(ANDROID_SDK_ROOT)/platforms/android-$(ANDROID_TARGET_SDK)/android.jar" || { echo "Android platform android-$(ANDROID_TARGET_SDK) is missing"; exit 1; }
|
||||||
@mkdir -p android
|
@mkdir -p android
|
||||||
@zsh -lc 'tmpdir=$$(mktemp -d); \
|
@sh -ec 'tmpdir=$$(mktemp -d); \
|
||||||
trap '\''python3 -c "import shutil,sys; shutil.rmtree(sys.argv[1], ignore_errors=True)" "$$tmpdir"'\'' EXIT; \
|
trap "rm -rf $$tmpdir" EXIT; \
|
||||||
"$(JAVA_HOME)/bin/javac" \
|
"$(JAVA_HOME)/bin/javac" \
|
||||||
-classpath "$(ANDROID_SDK_ROOT)/platforms/android-$(ANDROID_TARGET_SDK)/android.jar" \
|
-classpath "$(ANDROID_SDK_ROOT)/platforms/android-$(ANDROID_TARGET_SDK)/android.jar" \
|
||||||
-d "$$tmpdir" \
|
-d "$$tmpdir" \
|
||||||
|
|||||||
@@ -90,10 +90,24 @@ go get -tool gioui.org/cmd/gogio@latest
|
|||||||
Package:
|
Package:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go tool gogio -target android -icon internal/assets/keepassgo-icon.png ./cmd/keepassgo
|
make apk
|
||||||
```
|
```
|
||||||
|
|
||||||
You will need the Android SDK and NDK installed and configured for real device or release packaging.
|
`make apk` prefers a local Java 25 install at `JAVA_HOME`. If that is not
|
||||||
|
available, it falls back to the repo-managed Docker build image, which also
|
||||||
|
uses Java 25. You still need the Android SDK and NDK installed and configured
|
||||||
|
for real device or release packaging.
|
||||||
|
|
||||||
|
Release package:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make apk-release
|
||||||
|
```
|
||||||
|
|
||||||
|
`make apk-release` is the production-signing path. It requires a dedicated
|
||||||
|
release keystore at `~/.config/keepassgo/android-release.keystore` and a
|
||||||
|
password file at `~/.config/keepassgo/android-release.pass`, unless you
|
||||||
|
override `RELEASE_SIGNKEY` and `RELEASE_SIGNPASS_FILE`.
|
||||||
|
|
||||||
## Automation
|
## Automation
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,9 @@
|
|||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="application/octet-stream" />
|
||||||
|
<data android:mimeType="application/x-keepass2" />
|
||||||
|
<data android:mimeType="application/vnd.keepass" />
|
||||||
<data android:scheme="content" android:pathPattern=".*\\.kdbx" />
|
<data android:scheme="content" android:pathPattern=".*\\.kdbx" />
|
||||||
<data android:scheme="file" android:pathPattern=".*\\.kdbx" />
|
<data android:scheme="file" android:pathPattern=".*\\.kdbx" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.julianfamily.keepassgo;
|
package org.julianfamily.keepassgo;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.ClipData;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
@@ -10,9 +11,11 @@ import android.util.Log;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
public final class SharedVaultImportActivity extends Activity {
|
public final class SharedVaultImportActivity extends Activity {
|
||||||
private static final String TAG = "KeePassGOImport";
|
private static final String TAG = "KeePassGOImport";
|
||||||
@@ -36,6 +39,7 @@ public final class SharedVaultImportActivity extends Activity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void handleIntent(Intent intent) {
|
private void handleIntent(Intent intent) {
|
||||||
|
logIntent(intent);
|
||||||
Uri uri = resolveSharedUri(intent);
|
Uri uri = resolveSharedUri(intent);
|
||||||
if (uri == null) {
|
if (uri == null) {
|
||||||
Log.i(TAG, "no shared vault URI on intent");
|
Log.i(TAG, "no shared vault URI on intent");
|
||||||
@@ -55,10 +59,29 @@ public final class SharedVaultImportActivity extends Activity {
|
|||||||
}
|
}
|
||||||
String action = intent.getAction();
|
String action = intent.getAction();
|
||||||
if (Intent.ACTION_SEND.equals(action)) {
|
if (Intent.ACTION_SEND.equals(action)) {
|
||||||
return intent.getParcelableExtra(Intent.EXTRA_STREAM);
|
Uri extraStream = intent.getParcelableExtra(Intent.EXTRA_STREAM);
|
||||||
|
if (extraStream != null) {
|
||||||
|
return extraStream;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
|
||||||
|
ArrayList<Uri> streams = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
|
||||||
|
if (streams != null && !streams.isEmpty()) {
|
||||||
|
return streams.get(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (Intent.ACTION_VIEW.equals(action)) {
|
if (Intent.ACTION_VIEW.equals(action)) {
|
||||||
return intent.getData();
|
Uri data = intent.getData();
|
||||||
|
if (data != null) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClipData clipData = intent.getClipData();
|
||||||
|
if (clipData != null && clipData.getItemCount() > 0) {
|
||||||
|
Uri clipUri = clipData.getItemAt(0).getUri();
|
||||||
|
if (clipUri != null) {
|
||||||
|
return clipUri;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -69,7 +92,7 @@ public final class SharedVaultImportActivity extends Activity {
|
|||||||
throw new IOException("failed to create " + dir.getAbsolutePath());
|
throw new IOException("failed to create " + dir.getAbsolutePath());
|
||||||
}
|
}
|
||||||
File pendingFile = new File(dir, "pending-shared-vault.kdbx");
|
File pendingFile = new File(dir, "pending-shared-vault.kdbx");
|
||||||
try (InputStream in = getContentResolver().openInputStream(uri)) {
|
try (InputStream in = openSharedInputStream(uri)) {
|
||||||
if (in == null) {
|
if (in == null) {
|
||||||
throw new IOException("failed to open shared vault stream");
|
throw new IOException("failed to open shared vault stream");
|
||||||
}
|
}
|
||||||
@@ -88,6 +111,17 @@ public final class SharedVaultImportActivity extends Activity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private InputStream openSharedInputStream(Uri uri) throws IOException {
|
||||||
|
if ("file".equalsIgnoreCase(uri.getScheme())) {
|
||||||
|
String path = uri.getPath();
|
||||||
|
if (path == null || path.trim().isEmpty()) {
|
||||||
|
throw new IOException("file URI is missing a path");
|
||||||
|
}
|
||||||
|
return new FileInputStream(new File(path));
|
||||||
|
}
|
||||||
|
return getContentResolver().openInputStream(uri);
|
||||||
|
}
|
||||||
|
|
||||||
private String resolveDisplayName(Uri uri) {
|
private String resolveDisplayName(Uri uri) {
|
||||||
String displayName = queryDisplayName(uri);
|
String displayName = queryDisplayName(uri);
|
||||||
if (!displayName.isEmpty()) {
|
if (!displayName.isEmpty()) {
|
||||||
@@ -123,6 +157,20 @@ public final class SharedVaultImportActivity extends Activity {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void logIntent(Intent intent) {
|
||||||
|
if (intent == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Log.i(TAG, "intent action=" + intent.getAction()
|
||||||
|
+ " type=" + intent.getType()
|
||||||
|
+ " data=" + intent.getData()
|
||||||
|
+ " flags=0x" + Integer.toHexString(intent.getFlags()));
|
||||||
|
ClipData clipData = intent.getClipData();
|
||||||
|
if (clipData != null) {
|
||||||
|
Log.i(TAG, "intent clip items=" + clipData.getItemCount());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void launchMainActivity() {
|
private void launchMainActivity() {
|
||||||
Intent launch = new Intent();
|
Intent launch = new Intent();
|
||||||
launch.setClassName(this, "org.gioui.GioActivity");
|
launch.setClassName(this, "org.gioui.GioActivity");
|
||||||
|
|||||||
@@ -1,292 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if err := run(); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "build_android_apk: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func run() error {
|
|
||||||
workDir, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
gogioDir, err := commandOutput("go", "list", "-m", "-f", "{{.Dir}}", "gioui.org/cmd")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
tempDir, err := os.MkdirTemp("", "keepassgo-gogio-")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(tempDir)
|
|
||||||
|
|
||||||
tempModule := filepath.Join(tempDir, "gioui-cmd")
|
|
||||||
if err := copyDir(gogioDir, tempModule); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
targetFile := filepath.Join(tempModule, "gogio", "androidbuild.go")
|
|
||||||
if err := patchAndroidBuild(targetFile); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := runCommand(tempModule, "gofmt", "-w", targetFile); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := refreshTempModuleDeps(tempModule); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
javaHome := os.Getenv("JAVA_HOME")
|
|
||||||
wrappedJavaHome := javaHome
|
|
||||||
var cleanup func()
|
|
||||||
if major := javaMajor(javaHome); major >= 26 {
|
|
||||||
wrappedJavaHome, cleanup, err = wrapJavaHome(javaHome)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer cleanup()
|
|
||||||
}
|
|
||||||
|
|
||||||
patchedArgs := append([]string(nil), os.Args[1:]...)
|
|
||||||
if len(patchedArgs) != 0 {
|
|
||||||
last := patchedArgs[len(patchedArgs)-1]
|
|
||||||
if strings.HasPrefix(last, ".") {
|
|
||||||
patchedArgs[len(patchedArgs)-1] = filepath.Join(workDir, last)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gogioFiles, err := filepath.Glob(filepath.Join(tempModule, "gogio", "*.go"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
filteredFiles := make([]string, 0, len(gogioFiles))
|
|
||||||
for _, file := range gogioFiles {
|
|
||||||
if strings.HasSuffix(file, "_test.go") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
filteredFiles = append(filteredFiles, file)
|
|
||||||
}
|
|
||||||
if len(filteredFiles) == 0 {
|
|
||||||
return fmt.Errorf("no gogio go files found in %s", tempModule)
|
|
||||||
}
|
|
||||||
args := append([]string{"run"}, filteredFiles...)
|
|
||||||
args = append(args, patchedArgs...)
|
|
||||||
cmd := exec.Command("go", args...)
|
|
||||||
cmd.Dir = workDir
|
|
||||||
cmd.Env = append(os.Environ(), "JAVA_HOME="+wrappedJavaHome)
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
return cmd.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
func patchAndroidBuild(path string) error {
|
|
||||||
data, err := os.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
content := string(data)
|
|
||||||
|
|
||||||
content = strings.Replace(content,
|
|
||||||
"\tAppName string\n}",
|
|
||||||
"\tAppName string\n\tApplicationSnippets string\n}",
|
|
||||||
1,
|
|
||||||
)
|
|
||||||
content = strings.Replace(content,
|
|
||||||
"\tif err := visitPkg(pkgs[0]); err != nil {\n\t\treturn err\n\t}\n\n\tif err := compileAndroid(tmpDir, tools, bi); err != nil {\n",
|
|
||||||
"\tif err := visitPkg(pkgs[0]); err != nil {\n\t\treturn err\n\t}\n\tmoduleRoot := androidModuleRoot(bi.pkgDir)\n\tmoduleAndroidJars, err := filepath.Glob(filepath.Join(moduleRoot, \"android\", \"*.jar\"))\n\tif err != nil {\n\t\treturn err\n\t}\n\textraJars = append(extraJars, moduleAndroidJars...)\n\n\tif err := compileAndroid(tmpDir, tools, bi); err != nil {\n",
|
|
||||||
1,
|
|
||||||
)
|
|
||||||
content = strings.Replace(content,
|
|
||||||
"\terr = os.WriteFile(filepath.Join(v21Dir, \"themes.xml\"), []byte(themesV21), 0660)\n\tif err != nil {\n\t\treturn err\n\t}\n\tresZip := filepath.Join(tmpDir, \"resources.zip\")\n",
|
|
||||||
"\terr = os.WriteFile(filepath.Join(v21Dir, \"themes.xml\"), []byte(themesV21), 0660)\n\tif err != nil {\n\t\treturn err\n\t}\n\tmoduleRoot := androidModuleRoot(bi.pkgDir)\n\tcustomResDir := filepath.Join(moduleRoot, \"android\", \"res\")\n\tif _, err := os.Stat(customResDir); err == nil {\n\t\tif err := copyAndroidDir(customResDir, resDir); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tresZip := filepath.Join(tmpDir, \"resources.zip\")\n",
|
|
||||||
1,
|
|
||||||
)
|
|
||||||
content = strings.Replace(content,
|
|
||||||
"\t\tIconSnip: iconSnip,\n\t\tAppName: appName,\n\t}\n",
|
|
||||||
"\t\tIconSnip: iconSnip,\n\t\tAppName: appName,\n\t\tApplicationSnippets: loadAndroidSnippet(filepath.Join(moduleRoot, \"android\", \"application_snippets.xml\")),\n\t}\n",
|
|
||||||
1,
|
|
||||||
)
|
|
||||||
content = strings.Replace(content,
|
|
||||||
"\t\t</activity>\n\t</application>\n</manifest>`)\n",
|
|
||||||
"\t\t</activity>\n{{.ApplicationSnippets}}\n\t</application>\n</manifest>`)\n",
|
|
||||||
1,
|
|
||||||
)
|
|
||||||
|
|
||||||
insertPos := strings.Index(content, "func findNDK(")
|
|
||||||
if insertPos < 0 {
|
|
||||||
return fmt.Errorf("findNDK not found")
|
|
||||||
}
|
|
||||||
helpers := `
|
|
||||||
func loadAndroidSnippet(path string) string {
|
|
||||||
data, err := os.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
content := strings.TrimSpace(string(data))
|
|
||||||
if content == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return "\n" + content
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyAndroidDir(src, dst string) error {
|
|
||||||
return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
rel, err := filepath.Rel(src, path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
target := filepath.Join(dst, rel)
|
|
||||||
if info.IsDir() {
|
|
||||||
return os.MkdirAll(target, 0755)
|
|
||||||
}
|
|
||||||
data, err := os.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return os.WriteFile(target, data, 0660)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func androidModuleRoot(start string) string {
|
|
||||||
dir := start
|
|
||||||
for {
|
|
||||||
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
|
|
||||||
return dir
|
|
||||||
}
|
|
||||||
parent := filepath.Dir(dir)
|
|
||||||
if parent == dir {
|
|
||||||
return start
|
|
||||||
}
|
|
||||||
dir = parent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
`
|
|
||||||
content = content[:insertPos] + helpers + content[insertPos:]
|
|
||||||
|
|
||||||
return os.WriteFile(path, []byte(content), 0o644)
|
|
||||||
}
|
|
||||||
|
|
||||||
func commandOutput(name string, args ...string) (string, error) {
|
|
||||||
cmd := exec.Command(name, args...)
|
|
||||||
out, err := cmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
var stderr []byte
|
|
||||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
|
||||||
stderr = exitErr.Stderr
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("%s %s failed: %s%s", name, strings.Join(args, " "), out, stderr)
|
|
||||||
}
|
|
||||||
return strings.TrimSpace(string(out)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func runCommand(dir, name string, args ...string) error {
|
|
||||||
cmd := exec.Command(name, args...)
|
|
||||||
cmd.Dir = dir
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
return cmd.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
func refreshTempModuleDeps(dir string) error {
|
|
||||||
overrides := []string{
|
|
||||||
"golang.org/x/image@v0.37.0",
|
|
||||||
"golang.org/x/mod@v0.33.0",
|
|
||||||
"golang.org/x/sync@v0.20.0",
|
|
||||||
"golang.org/x/text@v0.35.0",
|
|
||||||
"golang.org/x/tools@v0.42.0",
|
|
||||||
}
|
|
||||||
args := append([]string{"get"}, overrides...)
|
|
||||||
return runCommand(dir, "go", args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func javaMajor(javaHome string) int {
|
|
||||||
if strings.TrimSpace(javaHome) == "" {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
cmd := exec.Command(filepath.Join(javaHome, "bin", "java"), "-version")
|
|
||||||
output, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
matches := regexp.MustCompile(`version "([0-9]+)`).FindStringSubmatch(string(output))
|
|
||||||
if len(matches) != 2 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
var major int
|
|
||||||
if _, err := fmt.Sscanf(matches[1], "%d", &major); err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return major
|
|
||||||
}
|
|
||||||
|
|
||||||
func wrapJavaHome(javaHome string) (string, func(), error) {
|
|
||||||
tempDir, err := os.MkdirTemp("", "keepassgo-java-home-")
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
binDir := filepath.Join(tempDir, "bin")
|
|
||||||
if err := os.MkdirAll(binDir, 0o755); err != nil {
|
|
||||||
os.RemoveAll(tempDir)
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
realBin := filepath.Join(javaHome, "bin")
|
|
||||||
if err := os.WriteFile(filepath.Join(binDir, "javac"), []byte("#!/bin/sh\nexport JAVA_TOOL_OPTIONS=-Xint\nexec "+shellQuote(filepath.Join(realBin, "javac"))+" \"$@\"\n"), 0o755); err != nil {
|
|
||||||
os.RemoveAll(tempDir)
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
for _, name := range []string{"java", "jar"} {
|
|
||||||
if err := os.WriteFile(filepath.Join(binDir, name), []byte("#!/bin/sh\nexec "+shellQuote(filepath.Join(realBin, name))+" \"$@\"\n"), 0o755); err != nil {
|
|
||||||
os.RemoveAll(tempDir)
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tempDir, func() { os.RemoveAll(tempDir) }, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func shellQuote(value string) string {
|
|
||||||
return "'" + strings.ReplaceAll(value, "'", "'\\''") + "'"
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyDir(src, dst string) error {
|
|
||||||
return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
rel, err := filepath.Rel(src, path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
target := filepath.Join(dst, rel)
|
|
||||||
if info.IsDir() {
|
|
||||||
return os.MkdirAll(target, 0o755)
|
|
||||||
}
|
|
||||||
in, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer in.Close()
|
|
||||||
out, err := os.Create(target)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer out.Close()
|
|
||||||
if _, err := io.Copy(out, in); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return out.Close()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -2,8 +2,12 @@ module git.julianfamily.org/keepassgo
|
|||||||
|
|
||||||
go 1.26
|
go 1.26
|
||||||
|
|
||||||
|
replace gioui.org => git.julianfamily.org/joejulian/gio-patched v0.9.1-0.20260416220049-9bfa6bc1c2dc
|
||||||
|
|
||||||
|
replace gioui.org/cmd => git.julianfamily.org/joejulian/gio-cmd-patched v0.9.1-0.20260417040456-1762d36ddecc
|
||||||
|
|
||||||
require (
|
require (
|
||||||
gioui.org v0.8.0
|
gioui.org v0.9.0
|
||||||
gioui.org/x v0.8.0
|
gioui.org/x v0.8.0
|
||||||
github.com/atotto/clipboard v0.1.4
|
github.com/atotto/clipboard v0.1.4
|
||||||
github.com/tobischo/gokeepasslib/v3 v3.6.2
|
github.com/tobischo/gokeepasslib/v3 v3.6.2
|
||||||
@@ -193,7 +197,6 @@ require (
|
|||||||
go.uber.org/multierr v1.6.0 // indirect
|
go.uber.org/multierr v1.6.0 // indirect
|
||||||
go.uber.org/zap v1.24.0 // indirect
|
go.uber.org/zap v1.24.0 // indirect
|
||||||
golang.org/x/crypto v0.48.0 // indirect
|
golang.org/x/crypto v0.48.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
|
|
||||||
golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect
|
golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect
|
||||||
golang.org/x/image v0.37.0 // indirect
|
golang.org/x/image v0.37.0 // indirect
|
||||||
golang.org/x/mod v0.33.0 // indirect
|
golang.org/x/mod v0.33.0 // indirect
|
||||||
|
|||||||
@@ -37,15 +37,15 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
|
|||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY=
|
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY=
|
||||||
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA=
|
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA=
|
||||||
gioui.org v0.8.0 h1:QV5p5JvsmSmGiIXVYOKn6d9YDliTfjtLlVf5J+BZ9Pg=
|
|
||||||
gioui.org v0.8.0/go.mod h1:vEMmpxMOd/iwJhXvGVIzWEbxMWhnMQ9aByOGQdlQ8rc=
|
|
||||||
gioui.org/cmd v0.8.0 h1:oy5qOlc1UXcglc5HBCMZQELiIzQ2obhT98mw+SuWafQ=
|
|
||||||
gioui.org/cmd v0.8.0/go.mod h1:wKLAyAgRR25VMYFzGX2Ecia0m0Td562wDcZ3LaPHPTI=
|
|
||||||
gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
|
gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
|
||||||
gioui.org/shader v1.0.8 h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA=
|
gioui.org/shader v1.0.8 h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA=
|
||||||
gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
|
gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
|
||||||
gioui.org/x v0.8.0 h1:RhIlQNOFKKn8D8FeaKKaXCo7vB3x+fq4VcD10HW/YpA=
|
gioui.org/x v0.8.0 h1:RhIlQNOFKKn8D8FeaKKaXCo7vB3x+fq4VcD10HW/YpA=
|
||||||
gioui.org/x v0.8.0/go.mod h1:aXtQb+kyqoUOjDl5/uMqAopjzVzMkeHBbMQOGT5KnSE=
|
gioui.org/x v0.8.0/go.mod h1:aXtQb+kyqoUOjDl5/uMqAopjzVzMkeHBbMQOGT5KnSE=
|
||||||
|
git.julianfamily.org/joejulian/gio-cmd-patched v0.9.1-0.20260417040456-1762d36ddecc h1:jyfCTx9wk/uLaEMkdKsg491C/kjfbG2EKAVTORhZxHo=
|
||||||
|
git.julianfamily.org/joejulian/gio-cmd-patched v0.9.1-0.20260417040456-1762d36ddecc/go.mod h1:RBQfFU8JCgMjQ2wKU9DG3zMC38TnY97E5MKoBGhGl3s=
|
||||||
|
git.julianfamily.org/joejulian/gio-patched v0.9.1-0.20260416220049-9bfa6bc1c2dc h1:p2AaZUAXa/ExPybNyeB05+GjTSZGA9lCfDpWz49IT5Y=
|
||||||
|
git.julianfamily.org/joejulian/gio-patched v0.9.1-0.20260416220049-9bfa6bc1c2dc/go.mod h1:BdI7mF5DCa3kxlo3G93XHL7khtZnk1gu4335pExk8gs=
|
||||||
git.wow.st/gmp/jni v0.0.0-20210610011705-34026c7e22d0 h1:bGG/g4ypjrCJoSvFrP5hafr9PPB5aw8SjcOWWila7ZI=
|
git.wow.st/gmp/jni v0.0.0-20210610011705-34026c7e22d0 h1:bGG/g4ypjrCJoSvFrP5hafr9PPB5aw8SjcOWWila7ZI=
|
||||||
git.wow.st/gmp/jni v0.0.0-20210610011705-34026c7e22d0/go.mod h1:+axXBRUTIDlCeE73IKeD/os7LoEnTKdkp8/gQOFjqyo=
|
git.wow.st/gmp/jni v0.0.0-20210610011705-34026c7e22d0/go.mod h1:+axXBRUTIDlCeE73IKeD/os7LoEnTKdkp8/gQOFjqyo=
|
||||||
github.com/4meepo/tagalign v1.4.2 h1:0hcLHPGMjDyM1gHG58cS73aQF8J4TdVR96TZViorO9E=
|
github.com/4meepo/tagalign v1.4.2 h1:0hcLHPGMjDyM1gHG58cS73aQF8J4TdVR96TZViorO9E=
|
||||||
@@ -132,10 +132,12 @@ github.com/charithe/durationcheck v0.0.10 h1:wgw73BiocdBDQPik+zcEoBG/ob8uyBHf2iy
|
|||||||
github.com/charithe/durationcheck v0.0.10/go.mod h1:bCWXb7gYRysD1CU3C+u4ceO49LoGOY1C1L6uouGNreQ=
|
github.com/charithe/durationcheck v0.0.10/go.mod h1:bCWXb7gYRysD1CU3C+u4ceO49LoGOY1C1L6uouGNreQ=
|
||||||
github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc=
|
github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc=
|
||||||
github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww=
|
github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww=
|
||||||
github.com/chromedp/cdproto v0.0.0-20191114225735-6626966fbae4 h1:QD3KxSJ59L2lxG6MXBjNHxiQO2RmxTQ3XcK+wO44WOg=
|
github.com/chromedp/cdproto v0.0.0-20250429231605-6ed5b53462d4 h1:UZdrvid2JFwnvPlUSEFlE794XZL4Jmrj8fuxfcLECJE=
|
||||||
github.com/chromedp/cdproto v0.0.0-20191114225735-6626966fbae4/go.mod h1:PfAWWKJqjlGFYJEidUM6aVIWPr0EpobeyVWEEmplX7g=
|
github.com/chromedp/cdproto v0.0.0-20250429231605-6ed5b53462d4/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k=
|
||||||
github.com/chromedp/chromedp v0.5.2 h1:W8xBXQuUnd2dZK0SN/lyVwsQM7KgW+kY5HGnntms194=
|
github.com/chromedp/chromedp v0.13.6 h1:xlNunMyzS5bu3r/QKrb3fzX6ow3WBQ6oao+J65PGZxk=
|
||||||
github.com/chromedp/chromedp v0.5.2/go.mod h1:rsTo/xRo23KZZwFmWk2Ui79rBaVRRATCjLzNQlOFSiA=
|
github.com/chromedp/chromedp v0.13.6/go.mod h1:h8GPP6ZtLMLsU8zFbTcb7ZDGCvCy8j/vRoFmRltQx9A=
|
||||||
|
github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM=
|
||||||
|
github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
@@ -180,6 +182,8 @@ github.com/go-critic/go-critic v0.12.0/go.mod h1:DpE0P6OVc6JzVYzmM5gq5jMU31zLr4a
|
|||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
|
github.com/go-json-experiment/json v0.0.0-20250417205406-170dfdcf87d1 h1:+VexzzkMLb1tnvpuQdGT/DicIRW7MN8ozsXqBMgp0Hk=
|
||||||
|
github.com/go-json-experiment/json v0.0.0-20250417205406-170dfdcf87d1/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||||
@@ -224,12 +228,12 @@ github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUW
|
|||||||
github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
|
github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
|
||||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
|
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||||
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
|
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||||
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
|
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
|
||||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
|
||||||
github.com/godbus/dbus/v5 v5.0.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro=
|
github.com/godbus/dbus/v5 v5.0.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro=
|
||||||
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
|
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
|
||||||
@@ -370,8 +374,6 @@ github.com/kisielk/errcheck v1.9.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0
|
|||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE=
|
github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE=
|
||||||
github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg=
|
github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg=
|
||||||
github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08 h1:V0an7KRw92wmJysvFvtqtKMAPmvS5O0jtB0nYo6t+gs=
|
|
||||||
github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08/go.mod h1:dFWs1zEqDjFtnBXsd1vPOZaLsESovai349994nHx3e0=
|
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
@@ -404,8 +406,6 @@ github.com/macabu/inamedparam v0.1.3 h1:2tk/phHkMlEL/1GNe/Yf6kkR/hkcUdAEY3L0hjYV
|
|||||||
github.com/macabu/inamedparam v0.1.3/go.mod h1:93FLICAIk/quk7eaPPQvbzihUdn/QkGDwIZEoLtpH6I=
|
github.com/macabu/inamedparam v0.1.3/go.mod h1:93FLICAIk/quk7eaPPQvbzihUdn/QkGDwIZEoLtpH6I=
|
||||||
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
|
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
|
||||||
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||||
github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM=
|
|
||||||
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
|
|
||||||
github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI=
|
github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI=
|
||||||
github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE=
|
github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE=
|
||||||
github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04=
|
github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04=
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
FROM golang:1.26-bookworm AS gobase
|
||||||
|
|
||||||
|
FROM eclipse-temurin:25-jdk
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
findutils \
|
||||||
|
git \
|
||||||
|
make \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY --from=gobase /usr/local/go /usr/local/go
|
||||||
|
|
||||||
|
ENV JAVA_HOME=/opt/java/openjdk
|
||||||
|
ENV PATH=/usr/local/go/bin:${PATH}
|
||||||
|
|
||||||
|
WORKDIR /workspace
|
||||||
Reference in New Issue
Block a user