Skip to content

Commit

Permalink
Allow to define project descriptor to allow for dependency specific f…
Browse files Browse the repository at this point in the history
…oreign code compilation (#3389)

* Support project settings and build configuration via properties file
  • Loading branch information
ekrich committed Oct 9, 2023
1 parent da2ca1a commit 6952d99
Show file tree
Hide file tree
Showing 7 changed files with 331 additions and 106 deletions.
133 changes: 133 additions & 0 deletions docs/user/native.rst
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,137 @@ transparent to the library user.
Using a library that contains native code can be used in combination with
the feature above that allows native code in your application.

EXPERIMENTAL: Deployment Descriptor for passing settings to the compiler
========================================================================

These are **experimental** features that were added because they are used
internally by Scala Native to simplify the build and organize the native
code with their respective projects. These features allow a library
developer that has native code included with their project to have
better control over compilation settings used for their project. By
adding a ``scala-native.properties`` file in the root of your project's
``resources/scala-native`` directory, settings can be added to the
properties file that are added to the compile command.

These features allow the settings described below to apply only to your
library during compilation.

Use the following procedure to use any of the features described below.

* Add a Scala Native deployment descriptor to your library.The properties file
must be named ``scala-native.properties`` and must be put in the base of the
``src/main/resources/scala-native`` directory.

Optional compilation of code if ``@link`` is found
--------------------------------------------------

Libraries developed with "glue" code as described in the previous section
can cause compilation errors when all the following conditions occur:

1. The library and/or header files are not installed

2. The dependency is in the library users' build

3. The code that uses the "glue" code is not called by the application
or library

If the glue "code" is being called, then the library and headers need to
be installed to compile your application otherwise errors are expected.

Scala Native code can include the annotation ``@link("z")`` for example
that says link with the ``z`` library. The compiler will add a link
option ``-lz`` for this library to the linking phase of the build if the code
with the annotation is used. See :ref:`interop`,
`Linking with native libraries` section for more information.

This **experimental** feature has been added so the users of your published
library can avoid the error described above. Use the following procedure to
implement this feature.

1. Add the following content to your new ``scala-native.properties`` file
desdribed above. For the purposes of this example assume the library is ``z``.
Note that if your library has more that one library you can add a comma
delimited list of libraries. If desired, the comments are not needed.

.. code-block:: properties
# configuration for glue code
# defines SCALANATIVE_LINK_Z if @link("z") annnotation is used (found in NIR)
# libraries used, comma delimited
nir.link.names = z
2. Now in your native "glue" code add the following. The macro is named
``SCALANATIVE_LINK_`` plus the uppercased name of the library.

.. code-block:: c
#ifdef SCALANATIVE_LINK_Z
#include <zlib.h>
int scalanative_z_no_flush() { return Z_NO_FLUSH; }
// other functions
#endif
The feature works by querying the NIR code to see if the user code is using the
``z`` library. If used, ``-DSCALANATIVE_LINK_Z`` is passed to the compiler
and your "glue" code is then compiled. Otherwise, the macro keeps the code
inside from compiling.

Adding defines to your library when code is being compiled
----------------------------------------------------------

If your library requires a C preprocessor define then use this feature to add
the define ``-DMY_DEFINE`` for example to the options passed to the compiler.

.. code-block:: properties
# add defines, do not add -D
preprocessor.defines = MY_DEFINE, MY_VALUE=2
Add extra include paths for your library
----------------------------------------

Currently, the native code compilation provides an include to your
project's ``resources/scala-native`` directory. This means
that code needs to use relative includes. e.g. ``#include "mylib.h"``
The build scans for all files to compile so only relative paths are
needed from your base ``scala-native`` directory

This feature allows you to vendor code, include code as is, that has
system includes. e.g. ``#include <libunwind.h>`` Add the path
starting from the ``scala-native`` path shown above. If you have a more
complex setup, you could also put your code in subdirectories and add
paths to them. Add the paths in Linux/UNIX style and they will be
converted as needed on the Windows platform.

.. code-block:: properties
# path to vendored libunwind a base gc path
compile.include.paths = platform/posix/libunwind, gc
Add unique identity to your library for debugging
-------------------------------------------------

Since these features can apply to libraries that are published,
those coordinates can be used to identify your library. The
example here is for a Scala Native ``javalib`` library.

.. code-block:: properties
# output via debugging
project.organization = org.scala-native
project.name = javalib
The descriptor and its settings are printed when compiling
in debug mode. Use the following command if using `sbt`:

.. code-block:: sh
sbt --debug
Other **experimental** features may be added for new requirements.

Continue to :ref:`testing`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# output for debugging
project.organization = org.scala-native
project.name = javalib

# defines SCALANATIVE_LINK_Z if @link("z") annnotation is used (found in NIR)
nir.link.names = z
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#ifdef SCALANATIVE_LINK_Z

#include <zlib.h>

int scalanative_z_no_flush() { return Z_NO_FLUSH; }
Expand Down Expand Up @@ -252,3 +254,4 @@ uLong scalanative_crc32(uLong crc, Bytef *buf, uInt len) {
uLong scalanative_crc32_combine(uLong crc1, uLong crc2, z_off_t len2) {
return crc32_combine(crc1, crc2, len2);
}
#endif
12 changes: 12 additions & 0 deletions nativelib/src/main/resources/scala-native/scala-native.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# output for debugging
project.orgainzation = org.scala-native
project.name = nativelib

# setup define based on GC selected
project.gcProject = true

# CG define
preprocessor.defines = NDEBUG

# path to vendored libunwind and GC
compile.include.paths = platform/posix/libunwind, gc
53 changes: 53 additions & 0 deletions tools/src/main/scala/scala/scalanative/build/Descriptor.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package scala.scalanative.build

import java.io.FileReader
import java.nio.file.Path
import java.util.Properties

import scala.util.Try
import java.io.Reader
import scala.annotation.tailrec

final case class Descriptor(
organization: Option[String],
name: Option[String],
gcProject: Boolean,
links: List[String],
defines: List[String],
includes: List[String]
)

object Descriptor {

def load(path: Path): Try[Descriptor] = Try {
var reader: Reader = null
try {
reader = new FileReader(path.toFile())
val props = new Properties()
props.load(reader)
Descriptor(
Option(props.getProperty("project.organization")),
Option(props.getProperty("project.name")),
props.getProperty("project.gcProject", "false").toBoolean,
parseStrings("nir.link.names", props),
parseStrings("preprocessor.defines", props),
parseStrings("compile.include.paths", props)
)
} finally {
if (reader != null) {
try {
reader.close()
} catch {
case t: Throwable =>
}
}
}
}

private def parseStrings(prop: String, props: Properties): List[String] =
Option(props.getProperty(prop)) match {
case Some(value) => value.split(',').map(_.trim()).toList
case None => List.empty
}

}
96 changes: 0 additions & 96 deletions tools/src/main/scala/scala/scalanative/build/Filter.scala

This file was deleted.

0 comments on commit 6952d99

Please sign in to comment.