Skip to content

Commit b11f838

Browse files
committed
[GR-40090] Support bundling language homes with native image
PullRequest: graal/12471
2 parents 7dbd7d8 + 7efb147 commit b11f838

File tree

17 files changed

+281
-18
lines changed

17 files changed

+281
-18
lines changed

common.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"README": "This file contains definitions that are useful for the hocon and jsonnet CI files of multiple repositories.",
33

4-
"mx_version" : "6.11.3",
4+
"mx_version" : "6.11.4",
55

66
"jdks": {
77
"openjdk11": {"name": "jpg-jdk", "version": "11.0.11", "build_id": "9", "open": true, "release": true, "platformspecific": true },

compiler/mx.compiler/suite.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
suite = {
2-
"mxversion" : "6.11.2",
2+
"mxversion" : "6.11.4",
33
"name" : "compiler",
44
"sourceinprojectwhitelist" : [],
55

docs/reference-manual/embedding/embed-languages.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -390,15 +390,26 @@ native-image --language:js -cp . HelloPolyglot
390390

391391
Please note that some languages (e.g. Python, Ruby) need their language home directories to work without limitations.
392392
If the polyglot application runs on a JVM (e.g. [here](#compile-and-run-a-polyglot-application)), the language homes are discovered automatically.
393-
However, for native images, the language homes have to be specified. We are currently working on a feature that will enable the `native-image` builder to automatically bundle
394-
the necessary language homes to the same directory as the produced executable/library. Until that feature is available, we recommend specifying
395-
a GraalVM home at runtime using the option `-Dorg.graalvm.home=$GRAALVM_HOME`, assuming the environment variable `GRAALVM_HOME` is populated with an absolute path to a GraalVM home directory.
396-
Language homes are automatically discovered in the specified directory. For example:
393+
However, for native images, paths to language homes have to be stored in the image or specified at runtime.
394+
395+
By default, the `native-image` builder copies the necessary language homes to the `resources` directory located in the same directory as the produced image.
396+
The paths to the copied homes are written to the image's build artifacts file and also stored in the image itself so that the homes are automatically discovered as long as their relative paths with respect to the image file stay the same.
397+
That means that the `resources` directory should be always distributed together with the image file.
397398

398399
```shell
399400
native-image --language:python -cp . HelloPolyglot
401+
./hellopolyglot
402+
```
403+
404+
In case an installed GraalVM is available, it is possible to use language homes from the GraalVM home directory. A GraalVM home can be specified at runtime using the option `-Dorg.graalvm.home=$GRAALVM_HOME`, assuming the environment variable `GRAALVM_HOME` is populated with an absolute path to the GraalVM home directory.
405+
Language homes are automatically discovered in the specified directory. For example:
406+
407+
```shell
400408
./hellopolyglot -Dorg.graalvm.home=$GRAALVM_HOME
401409
```
410+
411+
> Note: The `-Dorg.graalvm.home` option has precedence over any relative language home paths stored in the image.
412+
402413
> Note: The version of GraalVM the home of which is specified at runtime must match the version of GraalVM used to build the native executable/library.
403414
404415
### Excluding the JIT compiler

java-benchmarks/mx.java-benchmarks/suite.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
suite = {
2-
"mxversion" : "6.11.2",
2+
"mxversion" : "6.11.4",
33
"name": "java-benchmarks",
44

55
"ignore_suite_commit_info": True,

sdk/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ This changelog summarizes major changes between GraalVM SDK versions. The main f
88
* (GR-25849) (GR-41634) Added a new way to configure the IO access using the new class `IOAccess`. The IO access configuration determines how a guest language can access the host IO. The `IOAccess` class provides a predefined configuration to [disable](https://www.graalvm.org/sdk/javadoc/org/graalvm/polyglot/io/IOAccess.html#NONE) host IO access, or to [enable](https://www.graalvm.org/sdk/javadoc/org/graalvm/polyglot/io/IOAccess.html#ALL) full host IO access. A custom configuration can be created using an IOAccess [builder](https://www.graalvm.org/sdk/javadoc/org/graalvm/polyglot/io/IOAccess.html#newBuilder--).
99
* Deprecated `Context.Builder#allowIO(boolean)` To migrate, use `builder.allowIO(IOAccess.ALL)` to enable unrestricted IO operations on the host system, or `builder.allowIO(IOAccess.NONE)` to disable IO operations.
1010
* Deprecated `Context.Builder#fileSystem(FileSystem)`. To migrate, use `builder.allowIO(IOAccess.newBuilder().fileSystem(fileSystem).build())`.
11+
* Added automatic copying of language resources for embedding Truffle languages in native image. Documentation available [here](https://www.graalvm.org/reference-manual/embed-languages/#build-native-executables-from-polyglot-applications).
1112

1213
## Version 22.3.0
1314
* (GR-39852) Native Image API: Added FieldValueTransformer API

sdk/mx.sdk/mx_sdk_vm.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,11 @@ def __init__(self, suite, name, short_name, license_files, third_party_license_f
399399

400400

401401
class GraalVmLanguage(GraalVmTruffleComponent):
402+
"""
403+
:param support_distributions: distributions the contents of which is added to the language's home directory.
404+
The contents of support distributions setting the `fileListPurpose` attribute to `native-image-resources` will end up as file list in the `native-image-resources.filelist` file in this language's home directory.
405+
As a part of a native image build that includes this language, the files in the merged file list will be copied as resources to a directory named `resources` next to the produced image.
406+
"""
402407
pass
403408

404409

sdk/mx.sdk/mx_sdk_vm_impl.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,7 @@ def __init__(self, suite, name, deps, components, is_graalvm, exclLibs, platform
353353
base_dir=None,
354354
path=None,
355355
stage1=False,
356+
include_native_image_resources_filelists=False,
356357
**kw_args): # pylint: disable=super-init-not-called
357358
self.components = components or registered_graalvm_components(stage1)
358359
self.stage1 = stage1
@@ -555,6 +556,8 @@ def _add_link(_dest, _target, _component=None, _dest_base_name=None):
555556
graalvm_dists = set() # the jar distributions mentioned by launchers and libraries
556557
component_dists = set() # the jar distributions directly mentioned by components
557558

559+
_lang_homes_with_ni_resources = []
560+
558561
for _component in sorted(self.components, key=lambda c: c.name):
559562
mx.logv('Adding {} ({}) to the {} {}'.format(_component.name, _component.__class__.__name__, name, self.__class__.__name__))
560563
_component_type_base = _get_component_type_base(_component)
@@ -584,6 +587,16 @@ def _add_link(_dest, _target, _component=None, _dest_base_name=None):
584587
'exclude': _component.license_files if _no_licenses() else [],
585588
'path': None,
586589
} for d in _component.support_distributions], _component)
590+
if include_native_image_resources_filelists and isinstance(_component, mx_sdk.GraalVmLanguage) and _component.support_distributions:
591+
# A support distribution of a GraalVmLanguage component might have the `fileListPurpose` attribute with value 'native-image-resources' specifying that all the files from the distribution should
592+
# be used as native image resources. Any value of the attribute other than 'native-image-resources' or None for a support distribution of a GraalVmLanguage is invalid. If the attribute is specified,
593+
# there is a '<distribution archive file path>.filename' file containing a file list of all the files from the distribution. The support distributions specifying the attribute together specify
594+
# a subset of files from this component's home directory. The file lists will be merged by the NativeImageResourcesFileList project into a single file `native-image-resources.filelist` that will be
595+
# written into this component's home directory. As a part of a native image build that includes this component, the files in the merged file list will be copied as resources to a directory named
596+
# `resources` next to the produced image. This impacts only the native images built by GraalVM that are not a part of the GraalVM itself.
597+
if not _component_base in _lang_homes_with_ni_resources:
598+
_add(layout, _component_base, 'dependency:{}/native-image-resources.filelist'.format(NativeImageResourcesFileList.project_name(_component.dir_name)), _component)
599+
_lang_homes_with_ni_resources.append(_component_base)
587600
_add(layout, '<jdk_base>/include/', [{
588601
'source_type': 'extracted-dependency',
589602
'dependency': d,
@@ -864,6 +877,7 @@ def __init__(self, base_name, theLicense=None, stage1=False, components=None, **
864877
base_dir=base_dir,
865878
path=None,
866879
stage1=stage1,
880+
include_native_image_resources_filelists=not stage1,
867881
**kw_args)
868882

869883
@staticmethod
@@ -1873,6 +1887,74 @@ def clean(self, forBuild=False):
18731887
def __str__(self):
18741888
return 'Building {}'.format(self.subject.name)
18751889

1890+
class NativeImageResourcesFileList(_with_metaclass(ABCMeta, GraalVmProject)):
1891+
def __init__(self, component, components, language_dir, deps, **kw_args):
1892+
super(NativeImageResourcesFileList, self).__init__(component, NativeImageResourcesFileList.project_name(language_dir), deps, **kw_args)
1893+
self.language_dir = language_dir
1894+
self.components = components
1895+
1896+
def native_image_resources_filelist_file(self):
1897+
return join(self.get_output_base(), self.name, "native-image-resources.filelist")
1898+
1899+
def getArchivableResults(self, use_relpath=True, single=False):
1900+
out = self.native_image_resources_filelist_file()
1901+
yield out, basename(out)
1902+
1903+
def get_containing_graalvm(self):
1904+
return get_final_graalvm_distribution()
1905+
1906+
def getBuildTask(self, args):
1907+
return NativeImageResourcesFileListBuildTask(self, args)
1908+
1909+
@staticmethod
1910+
def project_name(language_dir):
1911+
return "org.graalvm.langugage." + language_dir + ".ni_resources_filelist"
1912+
1913+
1914+
class NativeImageResourcesFileListBuildTask(_with_metaclass(ABCMeta, mx.ProjectBuildTask)):
1915+
def __init__(self, project, args):
1916+
super(NativeImageResourcesFileListBuildTask, self).__init__(args, 1, project)
1917+
self._native_image_resources_filelist_contents = None
1918+
1919+
def needsBuild(self, newestInput):
1920+
reason = _file_needs_build(newestInput, self.subject.native_image_resources_filelist_file(), self.native_image_resources_filelist_contents)
1921+
if reason:
1922+
return True, reason
1923+
return False, None
1924+
1925+
def native_image_resources_filelist_contents(self):
1926+
if self._native_image_resources_filelist_contents is None:
1927+
contents = []
1928+
for c in self.subject.components:
1929+
for dep in c.support_distributions:
1930+
d = mx.dependency(dep)
1931+
if d.fileListPurpose:
1932+
if d.fileListPurpose != 'native-image-resources':
1933+
mx.abort("Since distribution {} is a GraalVmLanguage support distribution, the only allowed value of its fileListPurpose attribute is 'native-image-resources', but was {}.".format(d.name, d.fileListPurpose))
1934+
(filelist_file,) = (p for p, n in d.getArchivableResults(single=False) if n.endswith(".filelist"))
1935+
if not exists(filelist_file):
1936+
mx.abort("Distribution {} specifies a fileListPurpose {}, but file {} was not found.".format(d.name, d.fileListPurpose, filelist_file))
1937+
with open(filelist_file, "r") as fp:
1938+
for line in fp:
1939+
contents.append(line.strip())
1940+
1941+
self._native_image_resources_filelist_contents = os.linesep.join(contents)
1942+
return self._native_image_resources_filelist_contents
1943+
1944+
def newestOutput(self):
1945+
paths = [self.subject.native_image_resources_filelist_file()]
1946+
return mx.TimeStampFile.newest(paths)
1947+
1948+
def build(self):
1949+
with mx.SafeFileCreation(self.subject.native_image_resources_filelist_file()) as sfc, io.open(sfc.tmpFd, mode='w', closefd=False, encoding='utf-8') as f:
1950+
f.write(self.native_image_resources_filelist_contents())
1951+
1952+
def clean(self, forBuild=False):
1953+
if exists(self.subject.native_image_resources_filelist_file()):
1954+
os.unlink(self.subject.native_image_resources_filelist_file())
1955+
1956+
def __str__(self):
1957+
return 'Building {}'.format(self.subject.name)
18761958

18771959
class GraalVmNativeImageBuildTask(_with_metaclass(ABCMeta, mx.ProjectBuildTask)):
18781960
def __init__(self, args, parallelism, project):
@@ -2860,6 +2942,7 @@ def mx_register_dynamic_suite_constituents(register_project, register_distributi
28602942
installables = {}
28612943
jvmci_parent_jars = []
28622944
modified_jmods = {}
2945+
dir_name_to_ni_resources_components = {}
28632946

28642947
for component in registered_graalvm_components(stage1=False):
28652948
if component.name in names:
@@ -2895,11 +2978,26 @@ def mx_register_dynamic_suite_constituents(register_project, register_distributi
28952978
register_project(launcher_project)
28962979
polyglot_config_project = PolyglotConfig(component, library_config)
28972980
register_project(polyglot_config_project)
2981+
if isinstance(component, mx_sdk.GraalVmLanguage) and component.support_distributions:
2982+
ni_resources_components = dir_name_to_ni_resources_components.get(component.dir_name)
2983+
if not ni_resources_components:
2984+
ni_resources_components = []
2985+
dir_name_to_ni_resources_components[component.dir_name] = ni_resources_components
2986+
ni_resources_components.append(component)
2987+
28982988
if component.installable and not _disable_installable(component):
28992989
installables.setdefault(component.installable_id, []).append(component)
29002990
if libpolyglot_component is not None and GraalVmLibPolyglotNativeProperties.needs_lib_polyglot_native_properties(component):
29012991
register_distribution(GraalVmLibPolyglotNativeProperties(component))
29022992

2993+
for dir_name in dir_name_to_ni_resources_components:
2994+
ni_resources_components = dir_name_to_ni_resources_components.get(dir_name)
2995+
deps = []
2996+
for component in ni_resources_components:
2997+
deps.extend(component.support_distributions)
2998+
native_image_resources_filelist_project = NativeImageResourcesFileList(None, ni_resources_components, dir_name, deps)
2999+
register_project(native_image_resources_filelist_project)
3000+
29033001
# Create installables
29043002
for components in installables.values():
29053003
main_component = _get_main_component(components)

sdk/mx.sdk/suite.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
# SOFTWARE.
4040
#
4141
suite = {
42-
"mxversion" : "6.11.2",
42+
"mxversion" : "6.11.4",
4343
"name" : "sdk",
4444
"version" : "23.0.0",
4545
"release" : False,

sdk/src/org.graalvm.home/src/org/graalvm/home/impl/DefaultHomeFinder.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,9 @@ public final class DefaultHomeFinder extends HomeFinder {
6565

6666
private static final boolean STATIC_VERBOSE = Boolean.getBoolean("com.oracle.graalvm.locator.verbose");
6767

68-
private static final Path FORCE_GRAAL_HOME;
69-
private static final Path GRAAL_HOME_RELATIVE_PATH;
70-
private static final Map<String, Path> LANGUAGE_RELATIVE_HOMES = new HashMap<>();
68+
static final Path FORCE_GRAAL_HOME;
69+
static final Path GRAAL_HOME_RELATIVE_PATH;
70+
static final Map<String, Path> LANGUAGE_RELATIVE_HOMES = new HashMap<>();
7171

7272
static {
7373
final String forcedHome = System.getProperty("org.graalvm.launcher.home");
@@ -275,6 +275,21 @@ public Map<String, Path> getToolHomes() {
275275
return res;
276276
}
277277

278+
/*
279+
* Sets a relative path to a language home if it wasn't already set by system properties that
280+
* are read in the static initializer of this class.
281+
*
282+
* NOTE: this method is called reflectively by TruffleBaseFeature
283+
*/
284+
@SuppressWarnings("unused")
285+
private static boolean setRelativeLanguageHomeIfNotAlreadySet(String languageId, Path homePath) {
286+
if (FORCE_GRAAL_HOME == null && GRAAL_HOME_RELATIVE_PATH == null && LANGUAGE_RELATIVE_HOMES.get(languageId) == null) {
287+
LANGUAGE_RELATIVE_HOMES.put(languageId, homePath);
288+
return true;
289+
}
290+
return false;
291+
}
292+
278293
private static Map<String, Path> collectHomes(Path folder) {
279294
Map<String, Path> res = new HashMap<>();
280295
if (Files.exists(folder)) {

substratevm/mx.substratevm/suite.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# pylint: disable=line-too-long
22
suite = {
3-
"mxversion" : "6.11.2",
3+
"mxversion" : "6.11.4",
44
"name": "substratevm",
55
"version" : "23.0.0",
66
"release" : False,

0 commit comments

Comments
 (0)