@ -13,6 +13,7 @@
# limitations under the License.
# limitations under the License.
from __future__ import annotations
from __future__ import annotations
from collections import defaultdict
from pathlib import PurePath
from pathlib import PurePath
import os
import os
import typing as T
import typing as T
@ -60,6 +61,7 @@ if T.TYPE_CHECKING:
unescaped_variables : T . Dict [ str , str ]
unescaped_variables : T . Dict [ str , str ]
unescaped_uninstalled_variables : T . Dict [ str , str ]
unescaped_uninstalled_variables : T . Dict [ str , str ]
already_warned_objs = set ( )
already_warned_objs = set ( )
_PKG_LIBRARIES : KwargInfo [ T . List [ T . Union [ str , dependencies . Dependency , build . SharedLibrary , build . StaticLibrary , build . CustomTarget , build . CustomTargetIndex ] ] ] = KwargInfo (
_PKG_LIBRARIES : KwargInfo [ T . List [ T . Union [ str , dependencies . Dependency , build . SharedLibrary , build . StaticLibrary , build . CustomTarget , build . CustomTargetIndex ] ] ] = KwargInfo (
@ -78,33 +80,39 @@ _PKG_REQUIRES: KwargInfo[T.List[T.Union[str, build.SharedLibrary, build.StaticLi
listify = True ,
listify = True ,
)
)
def _as_str ( obj : object ) - > str :
assert isinstance ( obj , str )
return obj
class DependenciesHelper :
class DependenciesHelper :
def __init__ ( self , state : ModuleState , name : str ) - > None :
def __init__ ( self , state : ModuleState , name : str ) - > None :
self . state = state
self . state = state
self . name = name
self . name = name
self . pub_libs = [ ]
self . pub_libs : T . List [ LIBS ] = [ ]
self . pub_reqs = [ ]
self . pub_reqs : T . List [ str ] = [ ]
self . priv_libs = [ ]
self . priv_libs : T . List [ LIBS ] = [ ]
self . priv_reqs = [ ]
self . priv_reqs : T . List [ str ] = [ ]
self . cflags = [ ]
self . cflags : T . List [ str ] = [ ]
self . version_reqs = { }
self . version_reqs : T . DefaultDict [ str , T . Set [ str ] ] = defaultdict ( set )
self . link_whole_targets = [ ]
self . link_whole_targets : T . List [ T . Union [ build . CustomTarget , build . CustomTargetIndex , build . StaticLibrary ] ] = [ ]
def add_pub_libs ( self , libs ) - > None :
def add_pub_libs ( self , libs : T . Sequence [ ANY_DEP ] ) - > None :
libs , reqs , cflags = self . _process_libs ( libs , True )
p_ libs, reqs , cflags = self . _process_libs ( libs , True )
self . pub_libs = libs + self . pub_libs # prepend to preserve dependencies
self . pub_libs = p_ libs + self . pub_libs # prepend to preserve dependencies
self . pub_reqs + = reqs
self . pub_reqs + = reqs
self . cflags + = cflags
self . cflags + = cflags
def add_priv_libs ( self , libs ) - > None :
def add_priv_libs ( self , libs : T . Sequence [ ANY_DEP ] ) - > None :
libs , reqs , _ = self . _process_libs ( libs , False )
p_ libs, reqs , _ = self . _process_libs ( libs , False )
self . priv_libs = libs + self . priv_libs
self . priv_libs = p_ libs + self . priv_libs
self . priv_reqs + = reqs
self . priv_reqs + = reqs
def add_pub_reqs ( self , reqs ) - > None :
def add_pub_reqs ( self , reqs : T . Sequence [ T . Union [ str , dependencies . Dependency ] ] ) - > None :
self . pub_reqs + = self . _process_reqs ( reqs )
self . pub_reqs + = self . _process_reqs ( reqs )
def add_priv_reqs ( self , reqs ) - > None :
def add_priv_reqs ( self , reqs : T . Sequence [ T . Union [ str , dependencies . Dependency ] ] ) - > None :
self . priv_reqs + = self . _process_reqs ( reqs )
self . priv_reqs + = self . _process_reqs ( reqs )
def _check_generated_pc_deprecation ( self , obj ) - > None :
def _check_generated_pc_deprecation ( self , obj ) - > None :
@ -124,9 +132,9 @@ class DependenciesHelper:
location = obj . generated_pc_warn [ 1 ] )
location = obj . generated_pc_warn [ 1 ] )
already_warned_objs . add ( ( name , obj . name ) )
already_warned_objs . add ( ( name , obj . name ) )
def _process_reqs ( self , reqs : T . List [ T . Union [ str , dependencies . Dependency ] ] ) :
def _process_reqs ( self , reqs : T . Sequence [ T . Union [ str , dependencies . Dependency ] ] ) - > T . List [ str ] :
''' Returns string names of requirements '''
''' Returns string names of requirements '''
processed_reqs = [ ]
processed_reqs : T . List [ str ] = [ ]
for obj in mesonlib . listify ( reqs ) :
for obj in mesonlib . listify ( reqs ) :
if not isinstance ( obj , str ) :
if not isinstance ( obj , str ) :
FeatureNew . single_use ( ' pkgconfig.generate requirement from non-string object ' , ' 0.46.0 ' , self . state . subproject )
FeatureNew . single_use ( ' pkgconfig.generate requirement from non-string object ' , ' 0.46.0 ' , self . state . subproject )
@ -151,14 +159,16 @@ class DependenciesHelper:
f ' or pkgconfig-dependency object, got { obj !r} ' )
f ' or pkgconfig-dependency object, got { obj !r} ' )
return processed_reqs
return processed_reqs
def add_cflags ( self , cflags ) - > None :
def add_cflags ( self , cflags : T . List [ str ] ) - > None :
self . cflags + = mesonlib . stringlistify ( cflags )
self . cflags + = mesonlib . stringlistify ( cflags )
def _process_libs ( self , libs , public : bool ) - > T . Tuple [ T . List , T . List , T . List ] :
def _process_libs (
self , libs : T . Sequence [ ANY_DEP ] , public : bool
) - > T . Tuple [ T . List [ T . Union [ str , build . SharedLibrary , build . StaticLibrary , build . CustomTarget , build . CustomTargetIndex ] ] , T . List [ str ] , T . List [ str ] ] :
libs = mesonlib . listify ( libs )
libs = mesonlib . listify ( libs )
processed_libs = [ ]
processed_libs : T . List [ T . Union [ str , build . SharedLibrary , build . StaticLibrary , build . CustomTarget , build . CustomTargetIndex ] ] = [ ]
processed_reqs = [ ]
processed_reqs : T . List [ str ] = [ ]
processed_cflags = [ ]
processed_cflags : T . List [ str ] = [ ]
for obj in libs :
for obj in libs :
if hasattr ( obj , ' generated_pc ' ) :
if hasattr ( obj , ' generated_pc ' ) :
self . _check_generated_pc_deprecation ( obj )
self . _check_generated_pc_deprecation ( obj )
@ -207,7 +217,12 @@ class DependenciesHelper:
return processed_libs , processed_reqs , processed_cflags
return processed_libs , processed_reqs , processed_cflags
def _add_lib_dependencies ( self , link_targets , link_whole_targets , external_deps , public , private_external_deps : bool = False ) - > None :
def _add_lib_dependencies (
self , link_targets : T . List [ T . Union [ build . StaticLibrary , build . SharedLibrary , build . CustomTarget , build . CustomTargetIndex ] ] ,
link_whole_targets : T . List [ T . Union [ build . StaticLibrary , build . CustomTarget , build . CustomTargetIndex ] ] ,
external_deps : T . List [ dependencies . Dependency ] ,
public : bool ,
private_external_deps : bool = False ) - > None :
add_libs = self . add_pub_libs if public else self . add_priv_libs
add_libs = self . add_pub_libs if public else self . add_priv_libs
# Recursively add all linked libraries
# Recursively add all linked libraries
for t in link_targets :
for t in link_targets :
@ -225,7 +240,7 @@ class DependenciesHelper:
else :
else :
add_libs ( external_deps )
add_libs ( external_deps )
def _add_link_whole ( self , t , public ) - > None :
def _add_link_whole ( self , t : T . Union [ build . CustomTarget , build . CustomTargetIndex , build . StaticLibrary , build . SharedLibrary ] , public : bool ) - > None :
# Don't include static libraries that we link_whole. But we still need to
# Don't include static libraries that we link_whole. But we still need to
# include their dependencies: a static library we link_whole
# include their dependencies: a static library we link_whole
# could itself link to a shared library or an installed static library.
# could itself link to a shared library or an installed static library.
@ -236,10 +251,8 @@ class DependenciesHelper:
if isinstance ( t , build . BuildTarget ) :
if isinstance ( t , build . BuildTarget ) :
self . _add_lib_dependencies ( t . link_targets , t . link_whole_targets , t . external_deps , public )
self . _add_lib_dependencies ( t . link_targets , t . link_whole_targets , t . external_deps , public )
def add_version_reqs ( self , name , version_reqs ) - > None :
def add_version_reqs ( self , name : str , version_reqs : T . Optional [ T . List [ str ] ] ) - > None :
if version_reqs :
if version_reqs :
if name not in self . version_reqs :
self . version_reqs [ name ] = set ( )
# Note that pkg-config is picky about whitespace.
# Note that pkg-config is picky about whitespace.
# 'foo > 1.2' is ok but 'foo>1.2' is not.
# 'foo > 1.2' is ok but 'foo>1.2' is not.
# foo, bar' is ok, but 'foo,bar' is not.
# foo, bar' is ok, but 'foo,bar' is not.
@ -260,8 +273,8 @@ class DependenciesHelper:
return op + ' ' + vreq [ len ( op ) : ]
return op + ' ' + vreq [ len ( op ) : ]
return vreq
return vreq
def format_reqs ( self , reqs ) - > str :
def format_reqs ( self , reqs : T . List [ str ] ) - > str :
result = [ ]
result : T . List [ str ] = [ ]
for name in reqs :
for name in reqs :
vreqs = self . version_reqs . get ( name , None )
vreqs = self . version_reqs . get ( name , None )
if vreqs :
if vreqs :
@ -272,11 +285,11 @@ class DependenciesHelper:
def remove_dups ( self ) - > None :
def remove_dups ( self ) - > None :
# Set of ids that have already been handled and should not be added any more
# Set of ids that have already been handled and should not be added any more
exclude = set ( )
exclude : T . Set [ build . Target ] = set ( )
# We can't just check if 'x' is excluded because we could have copies of
# We can't just check if 'x' is excluded because we could have copies of
# the same SharedLibrary object for example.
# the same SharedLibrary object for example.
def _ids ( x ) :
def _ids ( x : T . Union [ str , build . CustomTarget , build . CustomTargetIndex , build . StaticLibrary ] ) - > T . Iterable [ str ] :
if hasattr ( x , ' generated_pc ' ) :
if hasattr ( x , ' generated_pc ' ) :
yield x . generated_pc
yield x . generated_pc
if isinstance ( x , build . Target ) :
if isinstance ( x , build . Target ) :
@ -284,7 +297,7 @@ class DependenciesHelper:
yield x
yield x
# Exclude 'x' in all its forms and return if it was already excluded
# Exclude 'x' in all its forms and return if it was already excluded
def _add_exclude ( x ) :
def _add_exclude ( x : T . Union [ str , build . CustomTarget , build . CustomTargetIndex , build . StaticLibrary ] ) - > bool :
was_excluded = False
was_excluded = False
for i in _ids ( x ) :
for i in _ids ( x ) :
if i in exclude :
if i in exclude :
@ -297,7 +310,17 @@ class DependenciesHelper:
for t in self . link_whole_targets :
for t in self . link_whole_targets :
_add_exclude ( t )
_add_exclude ( t )
def _fn ( xs , libs = False ) :
# Mypy thinks these overlap, but since List is invariant they don't,
# `List[str]`` is not a valid input to `List[str | BuildTarget]`.
# pylance/pyright gets this right, but for mypy we have to ignore the
# error
@T . overload
def _fn ( xs : T . List [ str ] , libs : bool = False ) - > T . List [ str ] : . . . # type: ignore
@T . overload
def _fn ( xs : T . List [ LIBS ] , libs : bool = False ) - > T . List [ LIBS ] : . . .
def _fn ( xs : T . Union [ T . List [ str ] , T . List [ LIBS ] ] , libs : bool = False ) - > T . Union [ T . List [ str ] , T . List [ LIBS ] ] :
# Remove duplicates whilst preserving original order
# Remove duplicates whilst preserving original order
result = [ ]
result = [ ]
for x in xs :
for x in xs :
@ -332,7 +355,8 @@ class PkgConfigModule(ExtensionModule):
' generate ' : self . generate ,
' generate ' : self . generate ,
} )
} )
def _get_lname ( self , l , msg , pcfile , is_custom_target ) :
def _get_lname ( self , l : T . Union [ build . SharedLibrary , build . StaticLibrary , build . CustomTarget , build . CustomTargetIndex ] ,
msg : str , pcfile : str , is_custom_target : bool ) - > str :
if is_custom_target :
if is_custom_target :
basename = os . path . basename ( l . get_filename ( ) )
basename = os . path . basename ( l . get_filename ( ) )
name = os . path . splitext ( basename ) [ 0 ]
name = os . path . splitext ( basename ) [ 0 ]
@ -356,7 +380,7 @@ class PkgConfigModule(ExtensionModule):
mlog . warning ( msg . format ( l . name , ' name_prefix ' , l . name , pcfile ) )
mlog . warning ( msg . format ( l . name , ' name_prefix ' , l . name , pcfile ) )
return l . name
return l . name
def _escape ( self , value ) :
def _escape ( self , value : T . Union [ str , PurePath ] ) - > str :
'''
'''
We cannot use quote_arg because it quotes with ' and " which does not
We cannot use quote_arg because it quotes with ' and " which does not
work with pkg - config and pkgconf at all .
work with pkg - config and pkgconf at all .
@ -368,7 +392,7 @@ class PkgConfigModule(ExtensionModule):
value = value . as_posix ( )
value = value . as_posix ( )
return value . replace ( ' ' , r ' \ ' )
return value . replace ( ' ' , r ' \ ' )
def _make_relative ( self , prefix , subdir ) :
def _make_relative ( self , prefix : T . Union [ PurePath , str ] , subdir : T . Union [ PurePath , str ] ) - > str :
prefix = PurePath ( prefix )
prefix = PurePath ( prefix )
subdir = PurePath ( subdir )
subdir = PurePath ( subdir )
try :
try :
@ -378,10 +402,14 @@ class PkgConfigModule(ExtensionModule):
# pathlib joining makes sure absolute libdir is not appended to '${prefix}'
# pathlib joining makes sure absolute libdir is not appended to '${prefix}'
return ( ' $ {prefix} ' / libdir ) . as_posix ( )
return ( ' $ {prefix} ' / libdir ) . as_posix ( )
def _generate_pkgconfig_file ( self , state : ModuleState , deps , subdirs , name , description ,
def _generate_pkgconfig_file ( self , state : ModuleState , deps : DependenciesHelper ,
url , version , pcfile , conflicts , variables ,
subdirs : T . List [ str ] , name : T . Optional [ str ] ,
unescaped_variables , uninstalled = False , dataonly = False ,
description : T . Optional [ str ] , url : str , version : str ,
pkgroot = None ) :
pcfile : str , conflicts : T . List [ str ] ,
variables : T . List [ T . Tuple [ str , str ] ] ,
unescaped_variables : T . List [ T . Tuple [ str , str ] ] ,
uninstalled : bool = False , dataonly : bool = False ,
pkgroot : T . Optional [ str ] = None ) - > None :
coredata = state . environment . get_coredata ( )
coredata = state . environment . get_coredata ( )
referenced_vars = set ( )
referenced_vars = set ( )
optnames = [ x . name for x in BUILTIN_DIR_OPTIONS . keys ( ) ]
optnames = [ x . name for x in BUILTIN_DIR_OPTIONS . keys ( ) ]
@ -426,7 +454,7 @@ class PkgConfigModule(ExtensionModule):
srcdir = PurePath ( state . environment . get_source_dir ( ) )
srcdir = PurePath ( state . environment . get_source_dir ( ) )
else :
else :
outdir = state . environment . scratch_dir
outdir = state . environment . scratch_dir
prefix = PurePath ( coredata . get_option ( mesonlib . OptionKey ( ' prefix ' ) ) )
prefix = PurePath ( _as_str ( coredata . get_option ( mesonlib . OptionKey ( ' prefix ' ) ) ) )
if pkgroot :
if pkgroot :
pkgroot = PurePath ( pkgroot )
pkgroot = PurePath ( pkgroot )
if not pkgroot . is_absolute ( ) :
if not pkgroot . is_absolute ( ) :
@ -443,7 +471,7 @@ class PkgConfigModule(ExtensionModule):
if optname == ' prefix ' :
if optname == ' prefix ' :
ofile . write ( ' prefix= {} \n ' . format ( self . _escape ( prefix ) ) )
ofile . write ( ' prefix= {} \n ' . format ( self . _escape ( prefix ) ) )
else :
else :
dirpath = PurePath ( coredata . get_option ( mesonlib . OptionKey ( optname ) ) )
dirpath = PurePath ( _as_str ( coredata . get_option ( mesonlib . OptionKey ( optname ) ) ) )
ofile . write ( ' {} = {} \n ' . format ( optname , self . _escape ( ' $ {prefix} ' / dirpath ) ) )
ofile . write ( ' {} = {} \n ' . format ( optname , self . _escape ( ' $ {prefix} ' / dirpath ) ) )
if uninstalled and not dataonly :
if uninstalled and not dataonly :
ofile . write ( ' srcdir= {} \n ' . format ( self . _escape ( srcdir ) ) )
ofile . write ( ' srcdir= {} \n ' . format ( self . _escape ( srcdir ) ) )
@ -469,7 +497,7 @@ class PkgConfigModule(ExtensionModule):
if len ( conflicts ) > 0 :
if len ( conflicts ) > 0 :
ofile . write ( ' Conflicts: {} \n ' . format ( ' ' . join ( conflicts ) ) )
ofile . write ( ' Conflicts: {} \n ' . format ( ' ' . join ( conflicts ) ) )
def generate_libs_flags ( libs ) :
def generate_libs_flags ( libs : T . List [ LIBS ] ) - > T . Iterable [ str ] :
msg = ' Library target {0!r} has {1!r} set. Compilers ' \
msg = ' Library target {0!r} has {1!r} set. Compilers ' \
' may not find it from its \' -l {2} \' linker flag in the ' \
' may not find it from its \' -l {2} \' linker flag in the ' \
' {3!r} pkg-config file. '
' {3!r} pkg-config file. '
@ -478,6 +506,7 @@ class PkgConfigModule(ExtensionModule):
if isinstance ( l , str ) :
if isinstance ( l , str ) :
yield l
yield l
else :
else :
install_dir : T . Union [ str , bool ]
if uninstalled :
if uninstalled :
install_dir = os . path . dirname ( state . backend . get_target_filename_abs ( l ) )
install_dir = os . path . dirname ( state . backend . get_target_filename_abs ( l ) )
else :
else :
@ -507,8 +536,8 @@ class PkgConfigModule(ExtensionModule):
if is_custom_target or ' cs ' not in l . compilers :
if is_custom_target or ' cs ' not in l . compilers :
yield f ' -l { lname } '
yield f ' -l { lname } '
def get_uninstalled_include_dirs ( libs ) :
def get_uninstalled_include_dirs ( libs : T . List [ LIBS ] ) - > T . List [ str ] :
result = [ ]
result : T . List [ str ] = [ ]
for l in libs :
for l in libs :
if isinstance ( l , ( str , build . CustomTarget , build . CustomTargetIndex ) ) :
if isinstance ( l , ( str , build . CustomTarget , build . CustomTargetIndex ) ) :
continue
continue
@ -522,7 +551,7 @@ class PkgConfigModule(ExtensionModule):
result . append ( path )
result . append ( path )
return result
return result
def generate_uninstalled_cflags ( libs ) :
def generate_uninstalled_cflags ( libs : T . List [ LIBS ] ) - > T . Iterable [ str ] :
for d in get_uninstalled_include_dirs ( libs ) :
for d in get_uninstalled_include_dirs ( libs ) :
for basedir in [ ' $ {prefix} ' , ' $ {srcdir} ' ] :
for basedir in [ ' $ {prefix} ' , ' $ {srcdir} ' ] :
path = PurePath ( basedir , d )
path = PurePath ( basedir , d )
@ -533,7 +562,7 @@ class PkgConfigModule(ExtensionModule):
if len ( deps . priv_libs ) > 0 :
if len ( deps . priv_libs ) > 0 :
ofile . write ( ' Libs.private: {} \n ' . format ( ' ' . join ( generate_libs_flags ( deps . priv_libs ) ) ) )
ofile . write ( ' Libs.private: {} \n ' . format ( ' ' . join ( generate_libs_flags ( deps . priv_libs ) ) ) )
cflags = [ ]
cflags : T . List [ str ] = [ ]
if uninstalled :
if uninstalled :
cflags + = generate_uninstalled_cflags ( deps . pub_libs + deps . priv_libs )
cflags + = generate_uninstalled_cflags ( deps . pub_libs + deps . priv_libs )
else :
else :
@ -680,5 +709,6 @@ class PkgConfigModule(ExtensionModule):
lib . generated_pc_warn = [ name , location ]
lib . generated_pc_warn = [ name , location ]
return ModuleReturnValue ( res , [ res ] )
return ModuleReturnValue ( res , [ res ] )
def initialize ( * args , * * kwargs ) :
return PkgConfigModule ( * args , * * kwargs )
def initialize ( interp : Interpreter ) - > PkgConfigModule :
return PkgConfigModule ( interp )