38
38
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39
39
# SOFTWARE.
40
40
#
41
+ import filecmp
41
42
import fnmatch
42
43
import itertools
43
44
import json
@@ -1679,31 +1680,37 @@ def resolveDeps(self):
1679
1680
delattr (self , 'ignore' )
1680
1681
1681
1682
1682
- class LibffiBuilderProject (mx . AbstractNativeProject , mx_native .NativeDependency ): # pylint: disable=too-many-ancestors
1683
+ class LibffiBuilderProject (mx_native .MultitargetProject ):
1683
1684
"""Project for building libffi from source.
1684
1685
1685
- The build is performed by:
1686
+ The build is performed for each toolchain by:
1686
1687
1. Extracting the sources,
1687
1688
2. Applying the platform dependent patches, and
1688
1689
3. Invoking the platform dependent builder that we delegate to.
1689
1690
"""
1690
1691
1691
- libs = property (lambda self : self .delegate .libs )
1692
-
1693
1692
def __init__ (self , suite , name , deps , workingSets , ** kwargs ):
1694
1693
subDir = 'src'
1695
1694
srcDirs = ['patches' ]
1696
1695
d = os .path .join (suite .dir , subDir , name )
1697
1696
super (LibffiBuilderProject , self ).__init__ (suite , name , subDir , srcDirs , deps , workingSets , d , ** kwargs )
1698
1697
1699
1698
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
+
1700
1707
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' ),
1703
1710
'static_lib' ,
1704
1711
deliverable = 'ffi' ,
1705
1712
cflags = ['-MD' , '-O2' , '-DFFI_STATIC_BUILD' ])
1706
- self . delegate ._source = dict (tree = ['include' ,
1713
+ delegate ._source = dict (tree = ['include' ,
1707
1714
'src' ,
1708
1715
os .path .join ('src' , 'x86' )],
1709
1716
files = {'.h' : [os .path .join ('include' , 'ffi.h' ),
@@ -1722,6 +1729,12 @@ class LibtoolNativeProject(mx.NativeProject, # pylint: disable=too-many-ancesto
1722
1729
mx_native .NativeDependency ):
1723
1730
include_dirs = property (lambda self : [os .path .join (self .getOutput (), 'include' )])
1724
1731
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
1725
1738
1726
1739
def getArchivableResults (self , use_relpath = True , single = False ):
1727
1740
for file_path , archive_path in super (LibtoolNativeProject , self ).getArchivableResults (use_relpath ):
@@ -1731,42 +1744,130 @@ def getArchivableResults(self, use_relpath=True, single=False):
1731
1744
assert path_in_lt_objdir , 'the first build result must be from LT_OBJDIR'
1732
1745
break
1733
1746
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:\n len({ self .ref_include_dirs } )\n !=\n len({ 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 ,
1735
1818
['.libs/libffi.a' ,
1736
1819
'include/ffi.h' ,
1737
1820
'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 )
1740
1824
configure_args = ['--disable-dependency-tracking' ,
1741
1825
'--disable-shared' ,
1742
1826
'--with-pic' ]
1743
1827
1744
1828
if mx .get_os () == 'darwin' :
1745
1829
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 )]
1746
1840
1747
1841
configure_args += [' CFLAGS="{}"' .format (' ' .join (['-g' , '-O3' , '-fvisibility=hidden' ] + (['-m64' ] if mx .get_os () == 'solaris' else []))),
1748
1842
'CPPFLAGS="-DNO_JAVA_RAW_API"' ]
1749
1843
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 ()),
1753
1847
CONFIGURE_ARGS = ' ' .join (configure_args )
1754
1848
)
1755
1849
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
1757
1856
1758
1857
def resolveDeps (self ):
1759
1858
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
1762
1864
1763
1865
@property
1764
1866
def sources (self ):
1765
1867
assert len (self .deps ) == 1 , '{} must depend only on its sources' .format (self .name )
1766
1868
return self .deps [0 ]
1767
1869
1768
- @property
1769
- def patches (self ):
1870
+ def patches (self , toolchain ):
1770
1871
"""A list of patches that will be applied during a build."""
1771
1872
def patch_dir (d ):
1772
1873
return os .path .join (self .source_dirs ()[0 ], d )
@@ -1778,36 +1879,62 @@ def get_patches(patchdir):
1778
1879
1779
1880
for p in get_patches (patch_dir ('common' )):
1780
1881
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 ))
1781
1884
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 ):
1783
1890
for p in get_patches (os_arch_dir ):
1784
1891
yield p
1785
1892
else :
1786
1893
for p in get_patches (patch_dir ('others' )):
1787
1894
yield p
1788
1895
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 )
1791
1899
1792
1900
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 )
1794
1906
1907
+ def _archivable_results (self , use_relpath , base_dir , file_path ):
1908
+ mx .abort ("Should not be reached" )
1795
1909
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`
1800
1927
1801
1928
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 )
1803
1930
1804
1931
def needsBuild (self , newestInput ):
1805
1932
is_needed , reason = super (LibffiBuildTask , self ).needsBuild (newestInput )
1806
1933
if is_needed :
1807
1934
return True , reason
1808
1935
1809
1936
output = self .newestOutput ()
1810
- newest_patch = mx .TimeStampFile .newest (self .subject .patches )
1937
+ newest_patch = mx .TimeStampFile .newest (self .subject .patches ( self . delegate . toolchain ) )
1811
1938
if newest_patch and output .isOlderThan (newest_patch ):
1812
1939
return True , '{} is older than {}' .format (output , newest_patch )
1813
1940
@@ -1818,22 +1945,25 @@ def newestOutput(self):
1818
1945
return None if output and not output .exists () else output
1819
1946
1820
1947
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 )
1822
1949
1823
1950
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 )
1825
1952
1826
1953
mx .log ('Applying patches...' )
1827
1954
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 ) :
1830
1957
mx .run (git_apply + [patch ], cwd = self .subject .suite .vc_dir )
1831
1958
1832
1959
self .delegate .logBuild ()
1833
1960
self .delegate .build ()
1834
1961
1962
+ if hasattr (self .delegate , 'verify_include_dirs' ):
1963
+ self .delegate .verify_include_dirs ()
1964
+
1835
1965
def clean (self , forBuild = False ):
1836
- mx .rmtree (self .subject . out_dir , ignore_errors = True )
1966
+ mx .rmtree (self .out_dir , ignore_errors = True )
1837
1967
1838
1968
1839
1969
0 commit comments