Skip to content
Merged
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
49 changes: 46 additions & 3 deletions docs/modules/libpkl/pages/index.adoc
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
= libpkl
include::ROOT:partial$component-attributes.adoc[]

libpkl is a C library for driving Pkl evaluation.

Expand Down Expand Up @@ -32,9 +33,10 @@ The archive has the following structure:
├── include
│ └── pkl.h
├── lib
│ ├── libpkl.<shared library extension>
│ ├── libpkl.<static library extension>
│ ├── libpkl.<dynamic library extension>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated: standardize on "shared library" instead of "dynamic library"

│ └── pkgconfig
│ ├── libpkl-static.pc
│ └── libpkl.pc
├── LICENSE.txt
├── README.md
Expand All @@ -43,7 +45,7 @@ The archive has the following structure:

The file extensions are as follows:
|===
|OS |Static Library Extension |Dynamic library extension
|OS |Static Library Extension |Shared library extension

|macOS
|`.a`
Expand All @@ -58,6 +60,44 @@ The file extensions are as follows:
|`.dll`
|===

=== Linking to the static vs. shared library

The simplest way to configure your compiler to link to either the static or the shared library is to use https://en.wikipedia.org/wiki/Pkg-config[pkg-config].

The libpkl library ships with two pkg-config files: `libpkl.pc`, and `libpkl-static.pc`.

The normal `libpkl.pc` links to the shared library.

To unambiguously link to the static library, use `libpkl-static.pc`.

For example, when using `cc`:

[source,shell]
----
# Link against the shared library
cc -o myprogram myprogram.c $(pkg-config --libs --cflags libpkl)

# Link against the static library
cc -o myprogram myprogram.c $(pkg-config --static --libs --cflags libpkl-static)
----


If libpkl is installed in a non-system directory, set `PKG_CONFIG_PATH` to the subpath `lib/pkgconfig` within the directory that contains libpkl. For example, given libpkl directory `/path/to/libpkl`, the environment variable should have `/path/to/libpkl/lib/pkgconfig`.

NOTE: Windows can use pkg-config through the https://www.msys2.org/docs/pkgconfig/[MSYS2 toolchain]. Using pkg-config on Windows implies compiling with gcc-style compiler like https://code.visualstudio.com/docs/cpp/config-mingw[MinGW], instead of MSVC.

[NOTE]
====
It's also possible to link to the static library with the normal `libpkl.pc`.

For example:

[source,shell]
----
cc -Wl,-Bstatic $(pkg-config --libs --cflags libpkl)
----
====

== Building libpkl from source

To build libpkl from source, the apple/pkl repository first must be cloned down to a host OS.
Expand All @@ -79,7 +119,10 @@ cd pkl/
./gradlew libpkl:buildNative
----

The resulting archive will be written to `libpkl/build/distributions`.
This results in two directories of interest:

* `libpkl/build/distributions/`: directory containing archives (`.zip` on Windows, `.tar.gz` on macOS/Linux) of C libraries.
* `libpkl/build/native-libs/<os>-<arch>`: directory containing the built library files (the inner structure of the archive).

Cross-compilation is not supported.

Expand Down
35 changes: 20 additions & 15 deletions libpkl/libpkl.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -251,16 +251,11 @@ val buildSharedLibrary =
frameworks.addAll("Foundation", "CoreServices")
linkerFlags.addAll("-current_version", project.version.toString())
linkerFlags.addAll("-compatibility_version", "0.1.0")

// libpkl_internal_s bundles Native Image's own JNI-named implementations of JDK native
// methods (e.g. Java_sun_nio_ch_UnixFileDispatcherImpl_write0), which by default get
// exported with global visibility. Loading libpkl.dylib into a JVM process (e.g. via JNA)
// then lets the host JVM's own native-method resolution bind to these internal
// implementations instead of the JDK's real ones, silently corrupting unrelated file I/O.
// Restrict the dylib's exported symbols to just the public pkl_* API to prevent this.
// prevent JNI symbols from being exported and clobbering the symbol table
val exportedSymbolsFile = file("src/main/c/pkl.exported_symbols")
inputs.file(exportedSymbolsFile)
linkerFlags.addAll("-exported_symbols_list", exportedSymbolsFile.absolutePath)
compilerArgs.addAll("-install_name", "@rpath/libpkl.dylib")
}

if (targetMachine.os.isWindows) {
Expand All @@ -276,9 +271,7 @@ val buildSharedLibrary =

if (targetMachine.os.isLinux) {
libraries.add("dl")

// Same symbol-collision hazard as the macOS case above (see comment there), but scoped via
// a GNU ld version script instead of an exported-symbols list.
// prevent JNI symbols from being exported and clobbering the symbol table
val versionScriptFile = file("src/main/c/pkl.map")
inputs.file(versionScriptFile)
linkerFlags.add("--version-script=${versionScriptFile.absolutePath}")
Expand Down Expand Up @@ -317,7 +310,7 @@ val distZip =
into(baseName)
}

tasks.assembleNative { dependsOn(distZip, distTar) }
tasks.assembleNative { dependsOn(distZip, distTar, processFiles) }

val processFiles =
tasks.register<Copy>("processFiles") {
Expand All @@ -329,9 +322,23 @@ val processFiles =
includeEmptyDirs = true
into(buildInfo.targetMachine.outputDir)

filesMatching("libpkl.pc") {
filter<ReplaceTokens>("tokens" to mapOf("version" to buildInfo.pklVersion))
val tokens = buildMap {
this["version"] = buildInfo.pklVersion
this["static_lib_filename"] = "libpkl.${buildInfo.os.staticLibraryExtension}"
this["extra_static_libs"] =
when {
buildInfo.os.isMacOS -> " -lz -framework Foundation -framework CoreServices"
buildInfo.os.isWindows -> ""
else -> " -lz"
}
this["extra_shared_libs"] =
when {
buildInfo.os.isMacOS -> " -lpkl"
buildInfo.os.isWindows -> " -l:libpkl_dll.lib"
else -> " -lpkl"
}
}
filesMatching("**/libpkl*.pc") { filter<ReplaceTokens>("tokens" to tokens) }
}

val testNativeJava =
Expand Down Expand Up @@ -428,8 +435,6 @@ val compileNativeTestDynamic =
libraryFiles.from(buildInfo.targetMachine.libraryDir.map { it.file("libpkl_dll.lib") })
} else {
libraries.add("pkl")
libraries.add("z")
libraries.add("pthread")
// Bake the library's location into the test executable so the dynamic loader can find
// libpkl.so/libpkl.dylib without needing LD_LIBRARY_PATH/DYLD_LIBRARY_PATH set at run time.
linkerFlags.add("-rpath")
Expand Down
11 changes: 11 additions & 0 deletions libpkl/src/main/files/lib/pkgconfig/libpkl-static.pc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
prefix=${pcfiledir}/../..
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
includedir=${prefix}/include

Name: libpkl
Description: C library for calling into Pkl's message passing API.
URL: https://pkl-lang.org/main/current/libpkl/index.html
Version: @version@
Libs: ${libdir}/@static_lib_filename@@extra_static_libs@
Cflags: -I${includedir}
2 changes: 1 addition & 1 deletion libpkl/src/main/files/lib/pkgconfig/libpkl.pc
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ Name: libpkl
Description: C library for calling into Pkl's message passing API.
URL: https://pkl-lang.org/main/current/libpkl/index.html
Version: @version@
Libs: -L${libdir} -lpkl -lz -lpthread
Libs: -L${libdir}@extra_shared_libs@
Cflags: -I${includedir}
Loading