Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 57 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,18 @@ Support for other architectures can be enabled by adding the corresponding Maven
</dependency>
```

**Supported platforms:** `Darwin`, `Windows`, `Linux`, `Alpine Linux`
**Supported platforms:** `Darwin`, `Windows`, `Linux`, `Alpine Linux`, `FreeBSD 13`, `FreeBSD 14`
**Supported architectures:** `amd64`, `i386`, `arm32v6`, `arm32v7`, `arm64v8`, `ppc64le`

Note that not all architectures are supported by all platforms, you can find an exhaustive list of all available artifacts here: https://mvnrepository.com/artifact/io.zonky.test.postgres

FreeBSD runtime compatibility verified so far:

- `freebsd13` artifact on FreeBSD `13.5`
- `freebsd13` artifact on FreeBSD `14.4`
- `freebsd14` artifact on FreeBSD `14.4`
- `freebsd14` artifact on FreeBSD `15.0`

## Building from Source
The project uses a [Gradle](http://gradle.org)-based build system. In the instructions
below, [`./gradlew`](http://vimeo.com/34436402) is invoked from the root of the source tree and serves as
Expand All @@ -72,6 +79,8 @@ a cross-platform, self-contained bootstrap mechanism for the build.
Be sure that your `JAVA_HOME` environment variable points to the `jdk1.6.0` folder
extracted from the JDK download.

The Gradle wrapper used in this project is based on Gradle `6.9.3`. On modern machines it is safest to run the build with Java `8`, `11` or `17`. Java `19+` is not supported by this Gradle version.

Compiling non-native architectures rely on emulation, so it is necessary to register `qemu-*-static` executables:

`docker run --rm --privileged multiarch/qemu-user-static:register --reset`
Expand Down Expand Up @@ -101,6 +110,52 @@ Builds only a single binary for a specified platform and architecture.

`./gradlew clean install -Pversion=18.3.0 -PpgVersion=18.3 -ParchName=arm64v8 -PdistName=alpine`

For the FreeBSD 13 amd64 artifact:

`./gradlew clean :custom-freebsd-platform:install -Pversion=18.3.0 -PpgVersion=18.3 -PdistName=freebsd13`

For the FreeBSD 14 amd64 artifact:

`./gradlew clean :custom-freebsd-platform:install -Pversion=18.3.0 -PpgVersion=18.3 -PdistName=freebsd14`

These builds run a FreeBSD guest inside QEMU from a Docker container. By default:

- `freebsd13` uses the official FreeBSD `13.5-RELEASE` installer image
- `freebsd14` uses the official FreeBSD `14.4-RELEASE` installer image

Downloads are cached under `.cache/freebsd-builder`.

To override the FreeBSD installer image or cache directory:

`./gradlew clean :custom-freebsd-platform:install -Pversion=18.3.0 -PpgVersion=18.3 -PdistName=freebsd13 -PfreebsdImageUrl=https://download.freebsd.org/releases/amd64/amd64/ISO-IMAGES/13.5/FreeBSD-13.5-RELEASE-amd64-disc1.iso.xz -PcacheDir=$PWD/.cache/freebsd-builder`

`./gradlew clean :custom-freebsd-platform:install -Pversion=18.3.0 -PpgVersion=18.3 -PdistName=freebsd14 -PfreebsdImageUrl=https://download.freebsd.org/releases/amd64/amd64/ISO-IMAGES/14.4/FreeBSD-14.4-RELEASE-amd64-disc1.iso.xz -PcacheDir=$PWD/.cache/freebsd-builder`

The generated runtime archive is named `postgres-freebsd13-x86_64.txz` or `postgres-freebsd14-x86_64.txz`, and the resulting jar artifact is named `embedded-postgres-binaries-freebsd13-amd64-<version>.jar` or `embedded-postgres-binaries-freebsd14-amd64-<version>.jar`.

Current FreeBSD runtime assumption:

- the bundled PostgreSQL binaries and shared libraries are packaged inside the artifact
- ICU data is expected to be available using the standard FreeBSD layout under `/usr/local/share/icu`

In other words, the current FreeBSD artifact is suitable for embedded PostgreSQL on a normal FreeBSD host, but it is not yet a fully self-contained ICU runtime.

### Test the FreeBSD artifact

After creating the jar, the FreeBSD smoke test can be run with:

`./gradlew :custom-freebsd-platform:test -Pversion=18.3.0 -PpgVersion=18.3 -PdistName=freebsd13`

`./gradlew :custom-freebsd-platform:test -Pversion=18.3.0 -PpgVersion=18.3 -PdistName=freebsd14`

This smoke test boots a disposable FreeBSD guest for the requested major version and verifies:

- `initdb`
- `pg_ctl`
- `SHOW SERVER_VERSION`
- `pgcrypto`
- `uuid-ossp`

It is also possible to include the PostGIS extension by passing the `postgisVersion` parameter, e.g. `-PpostgisVersion=2.5.2`. Note that this option is not (yet) available for Windows and Mac OS platforms.

Optional parameters:
Expand All @@ -112,7 +167,7 @@ Optional parameters:
- supported values: `amd64`, `i386`, `arm32v6`, `arm32v7`, `arm64v8`, `ppc64le`
- *distName*
- default value: debian-like distribution
- supported values: the default value or `alpine`
- supported values: the default value, `alpine`, `freebsd13` or `freebsd14`
- *dockerImage*
- default value: resolved based on the platform
- supported values: any supported docker image
Expand Down
122 changes: 120 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ ext {
archNameParam = project.findProperty('archName') ?: ''
distNameParam = project.findProperty('distName') ?: ''
dockerImageParam = project.findProperty('dockerImage') ?: ''
freebsdImageUrlParam = project.findProperty('freebsdImageUrl') ?: ''
cacheDirParam = project.findProperty('cacheDir') ?: ''
qemuPathParam = project.findProperty('qemuPath') ?: ''

pgMajorVersionParam = (pgVersionParam =~ /(\d+).+/).with { matches() ? it[0][1].toInteger() : null }
Expand Down Expand Up @@ -48,12 +50,23 @@ task validateInputs {
if (!project.version || project.version == 'unspecified') {
throw new GradleException("The 'version' property must be set")
}
if (distNameParam && distNameParam != 'alpine') {
throw new GradleException("Currently only the 'alpine' distribution is supported")
if (distNameParam && !(distNameParam in ['alpine', 'freebsd13', 'freebsd14'])) {
throw new GradleException("Currently only the 'alpine', 'freebsd13' and 'freebsd14' distributions are supported")
}
if (archNameParam && !(archNameParam ==~ /^[a-z0-9]+$/)) {
throw new GradleException("The 'archName' property must contain only alphanumeric characters")
}
if ((distNameParam in ['freebsd13', 'freebsd14']) && archNameParam && archNameParam != 'amd64') {
throw new GradleException("Currently only the 'amd64' architecture is supported for FreeBSD distributions")
}

if (distNameParam in ['freebsd13', 'freebsd14'] && freebsdImageUrlParam) {
def expectedMajor = extractFreebsdMajorFromDistName(distNameParam)
def actualMajor = extractFreebsdMajorFromImageUrl(freebsdImageUrlParam)
if (actualMajor != null && actualMajor != expectedMajor) {
throw new GradleException("The configured FreeBSD image URL targets major version ${actualMajor}, but distName=${distNameParam} expects FreeBSD ${expectedMajor}")
}
}
}
}

Expand Down Expand Up @@ -419,6 +432,78 @@ alpineVariants.each { variant ->
}
}

project(':custom-freebsd-platform') {
if (distNameParam in ['freebsd13', 'freebsd14']) {
def archName = archNameParam ?: 'amd64'
def freebsdDistName = distNameParam
def freebsdMajor = extractFreebsdMajorFromDistName(freebsdDistName)

task buildCustomFreebsdBundle(group: 'build (custom)', type: LazyExec, dependsOn: validateInputs) {
def dockerImage = { dockerImageParam ?: defaultFreebsdBuilderImage() }
def freebsdImageUrl = { freebsdImageUrlParam ?: defaultFreebsdImageUrl(freebsdMajor) }
def cacheDir = { cacheDirParam ?: "${rootDir}/.cache/freebsd-builder" }

doFirst {
println "archName: $archName"
println "distName: $freebsdDistName"
println "dockerImage: ${dockerImage()}"
println "freebsdImageUrl: ${freebsdImageUrl()}"
println "cacheDir: ${cacheDir()}"
println ''
}

inputs.property('pgVersion', pgVersionParam)
inputs.property('archName', archName)
inputs.property('dockerImage', dockerImage)
inputs.property('freebsdImageUrl', freebsdImageUrl)
inputs.property('cacheDir', cacheDir)

inputs.file("$rootDir/scripts/build-postgres-freebsd.sh")
inputs.file("$rootDir/scripts/build-postgres-freebsd-guest.sh")
outputs.dir("$temporaryDir/bundle")

workingDir temporaryDir
commandLine 'sh', "$rootDir/scripts/build-postgres-freebsd.sh",
'-v', "$pgVersionParam",
'-i', dockerImage,
'-u', freebsdImageUrl,
'-k', cacheDir
}

task customFreebsdJar(group: 'build (custom)', type: Jar) {
from buildCustomFreebsdBundle
include "postgres-${freebsdDistName}-${normalizeArchName(archName)}.txz"
appendix = "${freebsdDistName}-${archName}"
}

task testCustomFreebsdJar(group: 'build (custom)', type: LazyExec, dependsOn: [validateInputs, customFreebsdJar]) {
def dockerImage = { dockerImageParam ?: defaultFreebsdBuilderImage() }
def freebsdImageUrl = { freebsdImageUrlParam ?: defaultFreebsdImageUrl(freebsdMajor) }
def cacheDir = { cacheDirParam ?: "${rootDir}/.cache/freebsd-builder" }

inputs.property('pgVersion', pgVersionParam)
inputs.property('archName', archName)
inputs.property('dockerImage', dockerImage)
inputs.property('freebsdImageUrl', freebsdImageUrl)
inputs.property('cacheDir', cacheDir)

inputs.file("$rootDir/scripts/test-postgres-freebsd.sh")
inputs.file("$rootDir/scripts/test-postgres-freebsd-guest.sh")

workingDir customFreebsdJar.destinationDirectory
commandLine 'sh', "$rootDir/scripts/test-postgres-freebsd.sh",
'-j', "embedded-postgres-binaries-${freebsdDistName}-${archName}-${version}.jar",
'-z', "postgres-${freebsdDistName}-${normalizeArchName(archName)}.txz",
'-i', dockerImage,
'-u', freebsdImageUrl,
'-v', "$pgVersionParam",
'-k', cacheDir
}

artifacts.add('bundles', tasks.getByName('customFreebsdJar'))
}
}

subprojects {
task sourcesJar(type: Jar, dependsOn: classes) {
from sourceSets.main.allSource
Expand Down Expand Up @@ -613,6 +698,39 @@ static def defaultAlpineImage(archName, useEmulation) {
}
}

static def defaultFreebsdBuilderImage() {
return 'ubuntu:24.04'
}

static def defaultFreebsdImageUrl(int majorVersion) {
switch (majorVersion) {
case 13:
return defaultFreebsd13ImageUrl()
case 14:
return defaultFreebsd14ImageUrl()
default:
throw new GradleException("No default FreeBSD image URL is defined for major version ${majorVersion}")
}
}

static def defaultFreebsd13ImageUrl() {
return 'https://download.freebsd.org/releases/amd64/amd64/ISO-IMAGES/13.5/FreeBSD-13.5-RELEASE-amd64-disc1.iso.xz'
}

static def defaultFreebsd14ImageUrl() {
return 'https://download.freebsd.org/releases/amd64/amd64/ISO-IMAGES/14.4/FreeBSD-14.4-RELEASE-amd64-disc1.iso.xz'
}

static def extractFreebsdMajorFromDistName(String distName) {
def matcher = (distName ?: '') =~ /^freebsd(\d+)$/
return matcher.matches() ? matcher[0][1].toInteger() : null
}

static def extractFreebsdMajorFromImageUrl(String imageUrl) {
def matcher = (imageUrl ?: '') =~ /(?:^|[^0-9])(1[0-9])(?:\.[0-9]+)?-RELEASE/
return matcher.find() ? matcher.group(1).toInteger() : null
}

static def normalizeArchName(String input) {
String arch = input.toLowerCase(Locale.US).replaceAll('[^a-z0-9]+', '')

Expand Down
Loading