Skip to content

Commit 5e0cdf2

Browse files
authored
Merge pull request #5 from Tob1as864/claude/reqif-parser-git-maven-repo-ed8cjq
Publish releases to a git-hosted Maven repository
2 parents c3f698b + d758a37 commit 5e0cdf2

5 files changed

Lines changed: 421 additions & 0 deletions

File tree

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Publishes the Maven artifacts of this project into a separate, public Git
4+
# repository that holds a plain Maven repository layout. That repository is
5+
# served read-only (and token-free) through raw.githubusercontent.com and can
6+
# hold the artifacts of several libraries side by side, because Maven's layout
7+
# already namespaces them by groupId and artifactId.
8+
#
9+
# Usage:
10+
# .github/scripts/publish-maven-repo.sh [version]
11+
#
12+
# Without an argument the version from pom.xml is published as-is (typically a
13+
# SNAPSHOT). With an argument the pom version is set to it for the build only;
14+
# pom.xml is restored afterwards, so the working tree keeps its development
15+
# version and nothing has to be committed back to the source branch.
16+
#
17+
# Environment:
18+
# MAVEN_REPO_REMOTE git remote of the target repository
19+
# (default: git@github.com:Tob1as864/maven-repo.git)
20+
# MAVEN_REPO_BRANCH branch to publish to (default: main)
21+
# CHECKOUT_DIR where to clone it (default: .maven-repo)
22+
# PUSH set to "false" for a local dry run (default: true)
23+
24+
set -euo pipefail
25+
26+
REMOTE="${MAVEN_REPO_REMOTE:-git@github.com:Tob1as864/maven-repo.git}"
27+
BRANCH="${MAVEN_REPO_BRANCH:-main}"
28+
CHECKOUT_DIR="${CHECKOUT_DIR:-.maven-repo}"
29+
PUSH="${PUSH:-true}"
30+
RELEASE_VERSION="${1:-}"
31+
32+
MVN="${MVN:-mvn}"
33+
REPO_ROOT="$(git rev-parse --show-toplevel)"
34+
cd "$REPO_ROOT"
35+
36+
log() { printf '\n==> %s\n' "$*"; }
37+
38+
# --- clone the target repository ---------------------------------------------
39+
log "Cloning $REMOTE"
40+
rm -rf "$CHECKOUT_DIR"
41+
git clone --quiet --depth 1 "$REMOTE" "$CHECKOUT_DIR"
42+
43+
REPO_DIR="$(cd "$CHECKOUT_DIR" && pwd)"
44+
45+
# A freshly created repository has no commits, so HEAD is unborn and no branch
46+
# ref exists yet; -B creates the branch in that case and switches to it in all
47+
# others.
48+
if [ "$(git -C "$REPO_DIR" rev-parse --abbrev-ref HEAD)" != "$BRANCH" ]; then
49+
git -C "$REPO_DIR" checkout -q -B "$BRANCH"
50+
fi
51+
52+
# --- determine and validate the version to publish ---------------------------
53+
if [ -n "$RELEASE_VERSION" ]; then
54+
log "Setting project version to $RELEASE_VERSION"
55+
POM_BACKUP="$(mktemp)"
56+
cp pom.xml "$POM_BACKUP"
57+
# Restore the development version even when the build below fails.
58+
trap 'cp "$POM_BACKUP" "$REPO_ROOT/pom.xml"; rm -f "$POM_BACKUP"' EXIT
59+
"$MVN" -B --no-transfer-progress versions:set \
60+
-DnewVersion="$RELEASE_VERSION" -DgenerateBackupPoms=false
61+
fi
62+
63+
VERSION="$("$MVN" -B -q --no-transfer-progress help:evaluate \
64+
-Dexpression=project.version -DforceStdout)"
65+
GROUP_ID="$("$MVN" -B -q --no-transfer-progress help:evaluate \
66+
-Dexpression=project.groupId -DforceStdout)"
67+
ARTIFACT_ID="$("$MVN" -B -q --no-transfer-progress help:evaluate \
68+
-Dexpression=project.artifactId -DforceStdout)"
69+
ARTIFACT_DIR="$REPO_DIR/$(printf '%s' "$GROUP_ID" | tr '.' '/')/$ARTIFACT_ID/$VERSION"
70+
71+
case "$VERSION" in
72+
*-SNAPSHOT) ;;
73+
*)
74+
# Released versions are immutable: never silently overwrite one.
75+
if [ -d "$ARTIFACT_DIR" ]; then
76+
echo "ERROR: $GROUP_ID:$ARTIFACT_ID:$VERSION already exists in $REMOTE." >&2
77+
echo " Bump the version or delete it there first." >&2
78+
exit 1
79+
fi
80+
;;
81+
esac
82+
83+
# --- build and deploy into the checkout --------------------------------------
84+
log "Deploying $GROUP_ID:$ARTIFACT_ID:$VERSION into $REPO_DIR"
85+
"$MVN" -B --no-transfer-progress -Prelease clean deploy -Dmaven.repo.dir="$REPO_DIR"
86+
87+
# --- landing page ------------------------------------------------------------
88+
# The repository is shared by several libraries, so only write a README when it
89+
# has none yet; an existing one is maintained by hand and must not be clobbered.
90+
if [ ! -e "$REPO_DIR/README.md" ]; then
91+
cat > "$REPO_DIR/README.md" <<'README'
92+
# Maven repository
93+
94+
This repository holds released Java artifacts in Maven repository layout. Its
95+
contents are **generated** by the release workflows of the individual library
96+
repositories - do not commit here by hand.
97+
98+
Consume it without any authentication:
99+
100+
```xml
101+
<repositories>
102+
<repository>
103+
<id>tob1as864</id>
104+
<url>https://raw.githubusercontent.com/Tob1as864/maven-repo/main</url>
105+
</repository>
106+
</repositories>
107+
```
108+
109+
Then declare the library you need as an ordinary dependency. Browse the
110+
directory tree above for the available groupIds, artifacts and versions.
111+
README
112+
fi
113+
114+
# --- commit and push ----------------------------------------------------------
115+
git -C "$REPO_DIR" add -A
116+
if git -C "$REPO_DIR" diff --cached --quiet; then
117+
log "No changes to publish"
118+
exit 0
119+
fi
120+
121+
git -C "$REPO_DIR" commit -q -m "Publish $GROUP_ID:$ARTIFACT_ID $VERSION"
122+
log "Committed $GROUP_ID:$ARTIFACT_ID $VERSION"
123+
124+
if [ "$PUSH" != "true" ]; then
125+
log "PUSH=$PUSH - skipping push (dry run)"
126+
exit 0
127+
fi
128+
129+
for delay in 2 4 8 16 0; do
130+
if git -C "$REPO_DIR" push -u origin "$BRANCH"; then
131+
log "Pushed to $REMOTE ($BRANCH)"
132+
exit 0
133+
fi
134+
[ "$delay" -eq 0 ] && break
135+
echo "Push failed, retrying in ${delay}s ..." >&2
136+
sleep "$delay"
137+
done
138+
139+
echo "ERROR: could not push to $REMOTE" >&2
140+
exit 1

‎.github/workflows/release.yml‎

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
name: Publish to Maven repo
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*'
7+
workflow_dispatch:
8+
inputs:
9+
version:
10+
description: 'Version to publish, e.g. 1.2.0. Leave empty to publish the current pom version (SNAPSHOT).'
11+
required: false
12+
default: ''
13+
14+
# Only the checkout of this repository is needed; writing to the Maven
15+
# repository goes through the deploy key, not through GITHUB_TOKEN.
16+
permissions:
17+
contents: read
18+
19+
# The job pushes to a shared repository, so never run two of them at once.
20+
concurrency:
21+
group: maven-repo-publish
22+
cancel-in-progress: false
23+
24+
jobs:
25+
publish:
26+
runs-on: ubuntu-latest
27+
steps:
28+
- name: Checkout
29+
uses: actions/checkout@v4
30+
31+
- name: Set up JDK 17
32+
uses: actions/setup-java@v4
33+
with:
34+
distribution: temurin
35+
java-version: '17'
36+
cache: maven
37+
38+
- name: Determine version
39+
id: version
40+
run: |
41+
if [ "${{ github.event_name }}" = "push" ]; then
42+
# Tag v1.2.0 publishes version 1.2.0.
43+
echo "value=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
44+
else
45+
echo "value=${{ github.event.inputs.version }}" >> "$GITHUB_OUTPUT"
46+
fi
47+
48+
- name: Set up deploy key for the Maven repository
49+
env:
50+
DEPLOY_KEY: ${{ secrets.MAVEN_REPO_DEPLOY_KEY }}
51+
run: |
52+
if [ -z "$DEPLOY_KEY" ]; then
53+
echo "::error::Secret MAVEN_REPO_DEPLOY_KEY is not set. See the README" \
54+
"section 'One-time setup of the publishing credentials'."
55+
exit 1
56+
fi
57+
# A wrong key format fails much later with an opaque SSH error, so
58+
# reject the common mistakes here with an actionable message.
59+
case "$(printf '%s' "$DEPLOY_KEY" | head -n1)" in
60+
'-----BEGIN '*'PRIVATE KEY-----')
61+
;;
62+
'PuTTY-User-Key-File'*)
63+
echo "::error::MAVEN_REPO_DEPLOY_KEY holds a PuTTY .ppk key, which OpenSSH" \
64+
"cannot read. In PuTTYgen use Conversions -> Export OpenSSH key and" \
65+
"store that file's full contents instead."
66+
exit 1
67+
;;
68+
*)
69+
echo "::error::MAVEN_REPO_DEPLOY_KEY is not an OpenSSH private key. It must" \
70+
"contain the complete key file, starting with a line" \
71+
"'-----BEGIN OPENSSH PRIVATE KEY-----' - not a single line copied out" \
72+
"of it."
73+
exit 1
74+
;;
75+
esac
76+
mkdir -p ~/.ssh
77+
chmod 700 ~/.ssh
78+
# printf keeps the trailing newline OpenSSH requires; a here-string would not.
79+
printf '%s\n' "$DEPLOY_KEY" > ~/.ssh/id_ed25519
80+
chmod 600 ~/.ssh/id_ed25519
81+
ssh-keyscan -t rsa,ecdsa,ed25519 github.com >> ~/.ssh/known_hosts 2>/dev/null
82+
83+
- name: Configure git
84+
run: |
85+
git config --global user.name 'github-actions[bot]'
86+
git config --global user.email '41898282+github-actions[bot]@users.noreply.github.com'
87+
88+
- name: Publish
89+
run: .github/scripts/publish-maven-repo.sh "${{ steps.version.outputs.value }}"

‎.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ target/
33
*.class
44
.idea/
55
*.iml
6+
.maven-repo/

‎README.md‎

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,103 @@ document puts the ReqIF elements into the default namespace
1515
(`<REQ-IF xmlns="...">`) or into a prefixed one (`<rif:REQ-IF xmlns:rif="...">`).
1616
The same holds for the embedded XHTML (`xhtml:div`, `reqif-xhtml:div`, ...).
1717

18+
# Using reqif4j as a dependency
19+
20+
Released artifacts are published into the separate, public repository
21+
[Tob1as864/maven-repo](https://github.com/Tob1as864/maven-repo), which holds a
22+
plain Maven repository layout and is served over `raw.githubusercontent.com`.
23+
No GitHub token and no `settings.xml` entry is needed.
24+
25+
Maven:
26+
27+
```xml
28+
<repositories>
29+
<repository>
30+
<id>tob1as864</id>
31+
<url>https://raw.githubusercontent.com/Tob1as864/maven-repo/main</url>
32+
</repository>
33+
</repositories>
34+
35+
<dependencies>
36+
<dependency>
37+
<groupId>de.uni_stuttgart.ils</groupId>
38+
<artifactId>reqif4j</artifactId>
39+
<version>1.1.0</version>
40+
</dependency>
41+
</dependencies>
42+
```
43+
44+
The repository `<id>` is just a local name for the declaration — pick any name
45+
that is unique inside your own pom; it is unrelated to the library's
46+
`artifactId`. Its only technical purpose is linking a repository to matching
47+
`<server>` credentials or mirrors in `settings.xml`, neither of which this
48+
repository needs.
49+
50+
To use a development build, additionally allow snapshots for the repository.
51+
Maven enables them by default, so this is only needed if you switched them off:
52+
53+
```xml
54+
<snapshots><enabled>true</enabled></snapshots>
55+
```
56+
57+
Gradle:
58+
59+
```kotlin
60+
repositories {
61+
maven { url = uri("https://raw.githubusercontent.com/Tob1as864/maven-repo/main") }
62+
}
63+
64+
dependencies {
65+
implementation("de.uni_stuttgart.ils:reqif4j:1.1.0")
66+
}
67+
```
68+
69+
Sources and javadoc jars are published alongside every version, so IDEs can
70+
show the API documentation. Note that raw.githubusercontent.com is CDN-cached
71+
for a few minutes, so a freshly published version may not resolve immediately.
72+
73+
## Publishing a new version
74+
75+
`.github/workflows/release.yml` builds the artifacts and commits them into the
76+
`maven-repo` repository. It runs when a `v*` tag is pushed (tag `v1.2.0`
77+
publishes version `1.2.0`), or on demand via *Actions -> Publish to Maven repo
78+
-> Run workflow*, where an empty version input publishes the current SNAPSHOT.
79+
80+
Release versions are immutable: publishing a version that already exists there
81+
fails instead of overwriting it. The pom version is only changed for the build,
82+
so no version bump is committed to this repository.
83+
84+
The same publish step can be run locally, without pushing:
85+
86+
```
87+
PUSH=false .github/scripts/publish-maven-repo.sh 1.2.0
88+
```
89+
90+
### One-time setup of the publishing credentials
91+
92+
The workflow authenticates against `maven-repo` with an SSH deploy key, which
93+
grants write access to that one repository only:
94+
95+
1. Create the key pair locally, without a passphrase:
96+
`ssh-keygen -t ed25519 -C "reqif4j release workflow" -f maven-repo-key -N ""`
97+
2. In **Tob1as864/maven-repo** -> *Settings -> Deploy keys -> Add deploy key*:
98+
paste the contents of `maven-repo-key.pub` and tick **Allow write access**.
99+
3. In **this** repository -> *Settings -> Secrets and variables -> Actions ->
100+
New repository secret* (a repository secret, not an environment secret):
101+
name `MAVEN_REPO_DEPLOY_KEY`, value the **complete** contents of the private
102+
key file `maven-repo-key`, from `-----BEGIN OPENSSH PRIVATE KEY-----` through
103+
`-----END OPENSSH PRIVATE KEY-----`.
104+
4. Delete both local key files.
105+
106+
The secret must hold an OpenSSH private key in its original multi-line form;
107+
the workflow rejects anything else before it starts publishing. PuTTY's own
108+
`.ppk` format does not work - if you generate the key with PuTTYgen, use
109+
*Conversions -> Export OpenSSH key* and store that exported file's contents.
110+
The key must not have a passphrase, because the workflow runs unattended.
111+
112+
The same deploy key setup is repeated per library that publishes into
113+
`maven-repo`; each library repository gets its own key.
114+
18115
# Build & Test
19116
The project builds with Maven (Java 17+):
20117

0 commit comments

Comments
 (0)