Skip to content

Commit 3f5a4fe

Browse files
committed
Support multitarget for libffi project
1 parent a20396a commit 3f5a4fe

File tree

4 files changed

+256
-42
lines changed

4 files changed

+256
-42
lines changed

substratevm/src/com.oracle.svm.truffle.nfi/src/com/oracle/svm/truffle/nfi/libffi/LibFFIHeaderDirectives.java

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,11 @@
2727
import java.io.File;
2828
import java.util.Collections;
2929
import java.util.List;
30+
import java.util.function.Function;
3031

32+
import com.oracle.svm.core.OS;
33+
import com.oracle.svm.core.SubstrateOptions;
34+
import com.oracle.svm.core.SubstrateUtil;
3135
import org.graalvm.nativeimage.ImageSingletons;
3236
import org.graalvm.nativeimage.c.CContext;
3337

@@ -46,10 +50,32 @@ public List<String> getHeaderFiles() {
4650
return Collections.singletonList("<svm_libffi.h>");
4751
}
4852

53+
private static String basePath() {
54+
/* Find location of library directory based on known header name */
55+
String libffiHeader = ProjectHeaderFile.resolve("com.oracle.svm.libffi", "include/svm_libffi.h");
56+
File libffiHeaderPath = new File(libffiHeader.substring(1));
57+
58+
return libffiHeaderPath.getParentFile().getParent();
59+
}
60+
61+
private static String multitargetSuffix() {
62+
String os = OS.getCurrent().asPackageName();
63+
String arch = SubstrateUtil.getArchitectureName();
64+
String libc = OS.LINUX.isCurrent() ? SubstrateOptions.UseLibC.getValue() : "default";
65+
66+
return os + "-" + arch + File.separator + libc;
67+
}
68+
4969
@Override
5070
public List<String> getOptions() {
51-
String libffiHeader = ProjectHeaderFile.resolve("com.oracle.svm.libffi", "include/svm_libffi.h");
52-
String libffiPath = new File(libffiHeader.substring(1)).getParent();
53-
return Collections.singletonList("-I" + libffiPath);
71+
/* Add base and target specific include directories */
72+
Function<String, String> bp = multitarget -> "-I" + basePath() + File.separator + multitarget + "include";
73+
return List.of(bp.apply(""), bp.apply(multitargetSuffix() + File.separator));
74+
}
75+
76+
@Override
77+
public List<String> getLibraryPaths() {
78+
/* Add multitarget style path */
79+
return List.of(basePath() + File.separator + multitargetSuffix());
5480
}
5581
}

truffle/mx.truffle/mx_truffle.py

Lines changed: 163 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
3939
# SOFTWARE.
4040
#
41+
import filecmp
4142
import fnmatch
4243
import itertools
4344
import json
@@ -1679,31 +1680,37 @@ def resolveDeps(self):
16791680
delattr(self, 'ignore')
16801681

16811682

1682-
class LibffiBuilderProject(mx.AbstractNativeProject, mx_native.NativeDependency): # pylint: disable=too-many-ancestors
1683+
class LibffiBuilderProject(mx_native.MultitargetProject):
16831684
"""Project for building libffi from source.
16841685
1685-
The build is performed by:
1686+
The build is performed for each toolchain by:
16861687
1. Extracting the sources,
16871688
2. Applying the platform dependent patches, and
16881689
3. Invoking the platform dependent builder that we delegate to.
16891690
"""
16901691

1691-
libs = property(lambda self: self.delegate.libs)
1692-
16931692
def __init__(self, suite, name, deps, workingSets, **kwargs):
16941693
subDir = 'src'
16951694
srcDirs = ['patches']
16961695
d = os.path.join(suite.dir, subDir, name)
16971696
super(LibffiBuilderProject, self).__init__(suite, name, subDir, srcDirs, deps, workingSets, d, **kwargs)
16981697

16991698
self.out_dir = self.get_output_root()
1699+
self.delegates = {}
1700+
self.include_dirs = None
1701+
1702+
def _get_or_create_delegate(self, toolchain):
1703+
delegate = self.delegates.get(toolchain)
1704+
if delegate is not None:
1705+
return delegate
1706+
17001707
if mx.get_os() == 'windows':
1701-
self.delegate = mx_native.DefaultNativeProject(suite, name, subDir, [], [], None,
1702-
os.path.join(self.out_dir, 'libffi-3.4.8'),
1708+
delegate = mx_native.DefaultNativeProject(self.suite, self.name, self.subDir, [], [], None,
1709+
os.path.join(self.out_dir, toolchain.spec.target.subdir, 'libffi-3.4.8'),
17031710
'static_lib',
17041711
deliverable='ffi',
17051712
cflags=['-MD', '-O2', '-DFFI_STATIC_BUILD'])
1706-
self.delegate._source = dict(tree=['include',
1713+
delegate._source = dict(tree=['include',
17071714
'src',
17081715
os.path.join('src', 'x86')],
17091716
files={'.h': [os.path.join('include', 'ffi.h'),
@@ -1722,6 +1729,12 @@ class LibtoolNativeProject(mx.NativeProject, # pylint: disable=too-many-ancesto
17221729
mx_native.NativeDependency):
17231730
include_dirs = property(lambda self: [os.path.join(self.getOutput(), 'include')])
17241731
libs = property(lambda self: [next(self.getArchivableResults(single=True))[0]])
1732+
source_tree = [] # expected by NinjaManifestGenerator
1733+
1734+
def __init__(self, suite, name, subDir, srcDirs, deps, workingSets, results, output, d, refIncludeDirs, theLicense=None, testProject=False, vpath=False, **kwArgs):
1735+
super(LibtoolNativeProject, self).__init__(suite, name, subDir, srcDirs, deps, workingSets, results, output, d, theLicense, testProject, vpath, **kwArgs)
1736+
self.out_dir = self.get_output_root()
1737+
self.ref_include_dirs = refIncludeDirs
17251738

17261739
def getArchivableResults(self, use_relpath=True, single=False):
17271740
for file_path, archive_path in super(LibtoolNativeProject, self).getArchivableResults(use_relpath):
@@ -1731,42 +1744,130 @@ def getArchivableResults(self, use_relpath=True, single=False):
17311744
assert path_in_lt_objdir, 'the first build result must be from LT_OBJDIR'
17321745
break
17331746

1734-
self.delegate = LibtoolNativeProject(suite, name, subDir, [], [], None,
1747+
def getBuildTask(self, args):
1748+
return LibtoolNativeBuildTask(args, self, self.ref_include_dirs)
1749+
1750+
class LibtoolNativeBuildTask(mx.NativeBuildTask):
1751+
def __init__(self, args, project, refIncludeDirs):
1752+
super(LibtoolNativeBuildTask, self).__init__(args, project)
1753+
self.ref_include_dirs = refIncludeDirs
1754+
1755+
def _build_run_args(self):
1756+
cmdline, cwd, env = super(LibtoolNativeBuildTask, self)._build_run_args()
1757+
1758+
if env.get('CC') != os.environ.get('CC'):
1759+
mx.abort("super()._build_run_args() set CC unexpectedly.")
1760+
if 'CC' in env:
1761+
mx.warn(f"Current $CC ({env['CC']}) will be overridden for '{self}'.")
1762+
1763+
# extract CC from toolchain definition and pass it to configure via the environment
1764+
def _find_cc_var():
1765+
target = 'printCC'
1766+
filename = 'extract_toolchain_info.ninja'
1767+
1768+
with mx_native.NinjaManifestGenerator(self.subject, cwd, filename, toolchain=self.toolchain) as gen:
1769+
gen.comment("ninja file to extract toolchain paths")
1770+
1771+
gen.include(os.path.join(self.toolchain.get_path(), 'toolchain.ninja'))
1772+
gen.newline()
1773+
1774+
gen.n.rule(target + 'var', command='echo $CC')
1775+
gen.n.build(target, target + 'var')
1776+
gen.newline()
1777+
1778+
capture = mx.OutputCapture()
1779+
mx.run([mx_native.Ninja.binary, target, '-f', os.path.join(cwd, filename)], cwd=cwd, out=capture)
1780+
return capture.data.strip().split('\n')[-1]
1781+
1782+
env['CC'] = _find_cc_var()
1783+
return cmdline, cwd, env
1784+
1785+
def verify_include_dirs(self):
1786+
if self.ref_include_dirs is None:
1787+
return
1788+
1789+
len_ref = len(self.ref_include_dirs)
1790+
if len_ref != 1:
1791+
mx.abort(f"Expected only one include_dirs: {self.ref_include_dirs}")
1792+
if len(self.subject.include_dirs) != len_ref:
1793+
mx.abort(f"Number of include_dirs between delegates are not matching:\nlen({self.ref_include_dirs})\n!=\nlen({self.subject.include_dirs})")
1794+
1795+
def _list_header_files(directory):
1796+
return [file for file in os.listdir(directory) if file.endswith('.h')]
1797+
1798+
ref_header_files = _list_header_files(self.ref_include_dirs[0])
1799+
subject_header_files = _list_header_files(self.subject.include_dirs[0])
1800+
1801+
if len(ref_header_files) != 2 or len(subject_header_files) != 2:
1802+
mx.abort(f"Unexpected number of header files:\n{ref_header_files}\n{subject_header_files}")
1803+
1804+
for header in ['ffi.h', 'ffitarget.h']:
1805+
reference = os.path.join(self.ref_include_dirs[0], header)
1806+
h = os.path.join(self.subject.include_dirs[0], header)
1807+
1808+
if not os.path.exists(reference):
1809+
mx.abort(f"File {reference} expected but does not exist.")
1810+
if not os.path.exists(h):
1811+
mx.abort(f"File {h} expected but does not exist.")
1812+
1813+
if not filecmp.cmp(reference, h):
1814+
mx.abort(f"Content of {reference} and {h} are expected to be the same, but are not.")
1815+
1816+
1817+
delegate = LibtoolNativeProject(self.suite, self.name, self.subDir, [], [], None,
17351818
['.libs/libffi.a',
17361819
'include/ffi.h',
17371820
'include/ffitarget.h'],
1738-
os.path.join(self.out_dir, 'libffi-build'),
1739-
os.path.join(self.out_dir, 'libffi-3.4.8'))
1821+
os.path.join(self.out_dir, toolchain.spec.target.subdir, 'libffi-build'),
1822+
os.path.join(self.out_dir, toolchain.spec.target.subdir, 'libffi-3.4.8'),
1823+
self.include_dirs)
17401824
configure_args = ['--disable-dependency-tracking',
17411825
'--disable-shared',
17421826
'--with-pic']
17431827

17441828
if mx.get_os() == 'darwin':
17451829
configure_args += ['--disable-multi-os-directory']
1830+
else:
1831+
assert toolchain.spec.target.os == 'linux'
1832+
1833+
configure_arch = {'amd64': 'x86_64', 'aarch64': 'aarch64'}.get(toolchain.spec.target.arch)
1834+
assert configure_arch, "translation to configure style arch is not supported yet for " + str(toolchain.spec.target.arch)
1835+
1836+
configure_libc = {'glibc': 'gnu', 'musl': 'musl'}.get(toolchain.spec.target.libc)
1837+
assert configure_libc, "translation to configure style libc is not supported yet for" + str(toolchain.spec.target.libc)
1838+
1839+
configure_args += ['--host={}-pc-linux-{}'.format(configure_arch, configure_libc)]
17461840

17471841
configure_args += [' CFLAGS="{}"'.format(' '.join(['-g', '-O3', '-fvisibility=hidden'] + (['-m64'] if mx.get_os() == 'solaris' else []))),
17481842
'CPPFLAGS="-DNO_JAVA_RAW_API"']
17491843

1750-
self.delegate.buildEnv = dict(
1751-
SOURCES=os.path.basename(self.delegate.dir),
1752-
OUTPUT=os.path.basename(self.delegate.getOutput()),
1844+
delegate.buildEnv = dict(
1845+
SOURCES=os.path.basename(delegate.dir),
1846+
OUTPUT=os.path.basename(delegate.getOutput()),
17531847
CONFIGURE_ARGS=' '.join(configure_args)
17541848
)
17551849

1756-
self.include_dirs = self.delegate.include_dirs
1850+
if self.include_dirs is None:
1851+
# include files of first delegate are used by users of this project.
1852+
self.include_dirs = delegate.include_dirs
1853+
1854+
self.delegates[toolchain] = delegate
1855+
return delegate
17571856

17581857
def resolveDeps(self):
17591858
super(LibffiBuilderProject, self).resolveDeps()
1760-
self.delegate.resolveDeps()
1761-
self.buildDependencies += self.delegate.buildDependencies
1859+
1860+
for toolchain in self.toolchains:
1861+
delegate = self._get_or_create_delegate(toolchain)
1862+
delegate.resolveDeps()
1863+
self.buildDependencies += delegate.buildDependencies
17621864

17631865
@property
17641866
def sources(self):
17651867
assert len(self.deps) == 1, '{} must depend only on its sources'.format(self.name)
17661868
return self.deps[0]
17671869

1768-
@property
1769-
def patches(self):
1870+
def patches(self, toolchain):
17701871
"""A list of patches that will be applied during a build."""
17711872
def patch_dir(d):
17721873
return os.path.join(self.source_dirs()[0], d)
@@ -1778,36 +1879,62 @@ def get_patches(patchdir):
17781879

17791880
for p in get_patches(patch_dir('common')):
17801881
yield p
1882+
1883+
os_arch_libc_variant_dir = patch_dir('{}-{}-{}-{}'.format(mx.get_os(), mx.get_arch(), toolchain.spec.target.libc, toolchain.spec.target.variant))
17811884
os_arch_dir = patch_dir('{}-{}'.format(mx.get_os(), mx.get_arch()))
1782-
if os.path.exists(os_arch_dir):
1885+
1886+
if os.path.exists(os_arch_libc_variant_dir):
1887+
for p in get_patches(os_arch_libc_variant_dir):
1888+
yield p
1889+
elif os.path.exists(os_arch_dir):
17831890
for p in get_patches(os_arch_dir):
17841891
yield p
17851892
else:
17861893
for p in get_patches(patch_dir('others')):
17871894
yield p
17881895

1789-
def getBuildTask(self, args):
1790-
return LibffiBuildTask(args, self)
1896+
def _build_task(self, target_arch, args, toolchain=None):
1897+
project_delegate = self._get_or_create_delegate(toolchain)
1898+
return LibffiBuildTask(args, self, project_delegate, target_arch, toolchain)
17911899

17921900
def getArchivableResults(self, use_relpath=True, single=False):
1793-
return self.delegate.getArchivableResults(use_relpath, single)
1901+
# alas `_archivable_results` doesn't give use the toolchain in use.
1902+
for toolchain in self.toolchains:
1903+
for file_path, archive_path in self.delegates[toolchain].getArchivableResults(use_relpath, single):
1904+
subdir = toolchain.spec.target.subdir
1905+
yield file_path, os.path.join(subdir, archive_path)
17941906

1907+
def _archivable_results(self, use_relpath, base_dir, file_path):
1908+
mx.abort("Should not be reached")
17951909

1796-
class LibffiBuildTask(mx.AbstractNativeBuildTask):
1797-
def __init__(self, args, project):
1798-
super(LibffiBuildTask, self).__init__(args, project)
1799-
self.delegate = project.delegate.getBuildTask(args)
1910+
@property
1911+
def toolchain_kind(self):
1912+
# not a Ninja project, but extracting CC from a given Ninja toolchain definition
1913+
return "ninja"
1914+
1915+
def target_libs(self, target):
1916+
for toolchain, delegate in self.delegates.items():
1917+
if toolchain.spec.target == target:
1918+
return delegate.libs
1919+
mx.abort("could not find libs for target " + target.name)
1920+
1921+
class LibffiBuildTask(mx_native.TargetArchBuildTask):
1922+
def __init__(self, args, project, project_delegate, target_arch, toolchain=None):
1923+
super(LibffiBuildTask, self).__init__(args, project, target_arch, toolchain)
1924+
self.delegate = project_delegate.getBuildTask(args)
1925+
self.delegate.toolchain = toolchain
1926+
self.srcDir = os.path.basename(project_delegate.dir) # something like `libffi-3.4.6`
18001927

18011928
def __str__(self):
1802-
return 'Building {}'.format(self.subject.name)
1929+
return 'Building {} for target_arch {} and toolchain {}'.format(self.subject.name, self.target_arch, self.toolchain)
18031930

18041931
def needsBuild(self, newestInput):
18051932
is_needed, reason = super(LibffiBuildTask, self).needsBuild(newestInput)
18061933
if is_needed:
18071934
return True, reason
18081935

18091936
output = self.newestOutput()
1810-
newest_patch = mx.TimeStampFile.newest(self.subject.patches)
1937+
newest_patch = mx.TimeStampFile.newest(self.subject.patches(self.delegate.toolchain))
18111938
if newest_patch and output.isOlderThan(newest_patch):
18121939
return True, '{} is older than {}'.format(output, newest_patch)
18131940

@@ -1818,22 +1945,25 @@ def newestOutput(self):
18181945
return None if output and not output.exists() else output
18191946

18201947
def build(self):
1821-
assert not os.path.exists(self.subject.out_dir), '{} must be cleaned before build'.format(self.subject.name)
1948+
assert not os.path.exists(self.out_dir), '{} must be cleaned before build'.format(self.subject.name)
18221949

18231950
mx.log('Extracting {}...'.format(self.subject.sources))
1824-
mx.Extractor.create(self.subject.sources.get_path(False)).extract(self.subject.out_dir)
1951+
mx.Extractor.create(self.subject.sources.get_path(False)).extract(self.out_dir)
18251952

18261953
mx.log('Applying patches...')
18271954
git_apply = ['git', 'apply', '--whitespace=nowarn', '--unsafe-paths', '--directory',
1828-
os.path.realpath(self.subject.delegate.dir)]
1829-
for patch in self.subject.patches:
1955+
os.path.join(os.path.realpath(self.out_dir), self.srcDir)]
1956+
for patch in self.subject.patches(self.delegate.toolchain):
18301957
mx.run(git_apply + [patch], cwd=self.subject.suite.vc_dir)
18311958

18321959
self.delegate.logBuild()
18331960
self.delegate.build()
18341961

1962+
if hasattr(self.delegate, 'verify_include_dirs'):
1963+
self.delegate.verify_include_dirs()
1964+
18351965
def clean(self, forBuild=False):
1836-
mx.rmtree(self.subject.out_dir, ignore_errors=True)
1966+
mx.rmtree(self.out_dir, ignore_errors=True)
18371967

18381968

18391969

0 commit comments

Comments
 (0)