Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OS/2 support #14106

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
6 changes: 6 additions & 0 deletions docs/yaml/functions/shared_library.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,9 @@ kwargs:
Set the specific ABI to compile (when compiling rust).
- 'rust' (default): Create a "dylib" crate.
- 'c': Create a "cdylib" crate.

shortname:
type: str
since: 1.8.0
description: |
A string specifying a DLL name fitting to 8.3 limit on OS/2 of this shared library.
2 changes: 1 addition & 1 deletion mesonbuild/backend/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,7 @@ def get_executable_serialisation(
else:
if exe_cmd[0].endswith('.jar'):
exe_cmd = ['java', '-jar'] + exe_cmd
elif exe_cmd[0].endswith('.exe') and not (mesonlib.is_windows() or mesonlib.is_cygwin() or mesonlib.is_wsl()):
elif exe_cmd[0].endswith('.exe') and not (mesonlib.is_windows() or mesonlib.is_cygwin() or mesonlib.is_wsl() or machine.is_os2()):
exe_cmd = ['mono'] + exe_cmd
exe_wrapper = None

Expand Down
19 changes: 18 additions & 1 deletion mesonbuild/backend/ninjabackend.py
Original file line number Diff line number Diff line change
Expand Up @@ -2394,6 +2394,13 @@ def generate_dynamic_link_rules(self) -> None:
args = []
options = {}
self.add_rule(NinjaRule(rule, cmdlist, args, description, **options, extra=None))
if self.environment.machines[for_machine].is_os2() and complist:
rule = 'IMPORTLIB{}'.format(self.get_rule_suffix(for_machine))
description = 'Generating import library $out'
command = ['emximp']
args = ['-o', '$out', '$in']
options = {}
self.add_rule(NinjaRule(rule, command, args, description, **options, extra=None))

args = self.environment.get_build_command() + \
['--internal',
Expand Down Expand Up @@ -3292,7 +3299,12 @@ def get_target_shsym_filename(self, target):
return os.path.join(targetdir, target.get_filename() + '.symbols')

def generate_shsym(self, target) -> None:
target_file = self.get_target_filename(target)
# On OS/2, an import library is generated after linking a DLL, so
# if a DLL is used as a target, import library is not generated.
if self.environment.machines[target.for_machine].is_os2():
target_file = self.get_target_filename_for_linking(target)
else:
target_file = self.get_target_filename(target)
symname = self.get_target_shsym_filename(target)
elem = NinjaBuildElement(self.all_outputs, symname, 'SHSYM', target_file)
# The library we will actually link to, which is an import library on Windows (not the DLL)
Expand Down Expand Up @@ -3487,6 +3499,11 @@ def generate_link(self, target: build.BuildTarget, outname, obj_list, linker: T.
linker_base = linker.get_language() # Fixme.
if isinstance(target, build.SharedLibrary):
self.generate_shsym(target)
if self.environment.machines[target.for_machine].is_os2():
target_file = self.get_target_filename(target)
import_name = self.get_import_filename(target)
elem = NinjaBuildElement(self.all_outputs, import_name, 'IMPORTLIB', target_file)
self.add_build(elem)
crstr = self.get_rule_suffix(target.for_machine)
linker_rule = linker_base + '_LINKER' + crstr
# Create an empty commands list, and start adding link arguments from
Expand Down
27 changes: 25 additions & 2 deletions mesonbuild/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ class DFeatures(TypedDict):
cs_kwargs)

known_exe_kwargs = known_build_target_kwargs | {'implib', 'export_dynamic', 'pie', 'vs_module_defs'}
known_shlib_kwargs = known_build_target_kwargs | {'version', 'soversion', 'vs_module_defs', 'darwin_versions', 'rust_abi'}
known_shlib_kwargs = known_build_target_kwargs | {'version', 'soversion', 'vs_module_defs', 'darwin_versions', 'rust_abi', 'shortname'}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'shortname' needs to be added to the documentation for shared_library in the docs section.

Additionally, in interpreter/type_checking.py you need to add:

KwargInfo('shortname', (str, NoneType), since='1.8')

to the _EXCLUSIVE_SHARED_LIB_KWS list.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. I'll try.

known_shmod_kwargs = known_build_target_kwargs | {'vs_module_defs', 'rust_abi'}
known_stlib_kwargs = known_build_target_kwargs | {'pic', 'prelink', 'rust_abi'}
known_jar_kwargs = known_exe_kwargs | {'main_class', 'java_resources'}
Expand Down Expand Up @@ -2167,14 +2167,16 @@ def post_init(self) -> None:
# See our FAQ for more detailed rationale:
# https://mesonbuild.com/FAQ.html#why-does-building-my-project-with-msvc-output-static-libraries-called-libfooa
if not hasattr(self, 'prefix'):
self.prefix = 'lib'
self.prefix = '' if self.environment.machines[self.for_machine].is_os2() else 'lib'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this violate the rational for why we use 'lib as a prefix on Windows, even though that's not the standard thing to do?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If meson build system requires 'lib' prefix for a static lib, I'll do.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment above points to https://mesonbuild.com/FAQ.html#why-does-building-my-project-with-msvc-output-static-libraries-called-libfooa which describes the reasoning:

  • On Windows, we need to use .a in order to distinguish between import libraries (foo.lib) and static libraries (which we name with lib*.a to resolve the name clash).
  • it works well with the mingw toolchain, that can look this up on Windows using -lfoo

How does OS/2 handle this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On OS/2, there are no official distinctions between static libs and an import libs. However, conventionally gcc + kLIBC uses _dll.a and _dll.lib for import libs, _s.a and _s.lib for static libs. .a and .lib can be used for both. If .a and .lib are paired with _dll.a and _dll.lib, they are static libs. If paired with _s.a and _s.lib, they are import libs.

And linkers search libraries with -lfoo in the following order by default:

1. libfoo_dll.a
2. foo_dll.a
3. libfoo.a
4. foo.a
5. foo.dll (this requires additional option)

If -Zomf is used, .lib is searched prior to .a in every search order.

.a libs including _dll.a and _s.a are for aout format which are created by ar and linked by ld, but .lib libs including _dll.lib and _s.lib are for OMF format which are created created by emxomfar and linked by emxomfld.

if not hasattr(self, 'suffix'):
if self.uses_rust():
if self.rust_crate_type == 'rlib':
# default Rust static library suffix
self.suffix = 'rlib'
elif self.rust_crate_type == 'staticlib':
self.suffix = 'a'
elif self.environment.machines[self.for_machine].is_os2() and self.get_option(OptionKey('emxomf')):
self.suffix = 'lib'
else:
if 'c' in self.compilers and self.compilers['c'].get_id() == 'tasking':
self.suffix = 'ma' if self.options.get_value('b_lto') and not self.prelink else 'a'
Expand Down Expand Up @@ -2248,6 +2250,7 @@ def __init__(
# Max length 2, first element is compatibility_version, second is current_version
self.darwin_versions: T.Optional[T.Tuple[str, str]] = None
self.vs_module_defs = None
self.shortname = None
# The import library this target will generate
self.import_filename = None
# The debugging information file this target will generate
Expand Down Expand Up @@ -2372,6 +2375,15 @@ def determine_filenames(self):
suffix = 'so'
# Android doesn't support shared_library versioning
self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}'
elif self.environment.machines[self.for_machine].is_os2():
suffix = 'dll'
# Import library is called foo_dll.a or foo_dll.lib
import_filename_tpl = '{0.name}_dll'
import_filename_tpl += '.lib' if self.environment.coredata.get_option(OptionKey('emxomf')) else '.a'
self.filename_tpl = '{0.shortname}' if self.shortname else '{0.name}'
if self.soversion:
self.filename_tpl += '{0.soversion}'
self.filename_tpl += '.{0.suffix}'
else:
prefix = 'lib'
suffix = 'so'
Expand All @@ -2389,6 +2401,14 @@ def determine_filenames(self):
if self.suffix is None:
self.suffix = suffix
self.filename = self.filename_tpl.format(self)
if self.environment.machines[self.for_machine].is_os2():
# OS/2 does not allow a longer DLL name than 8 chars
name = os.path.splitext(self.filename)[0]
if len(name) > 8:
name = name[:8]
if self.soversion:
name = name[:-len(self.soversion)] + self.soversion
self.filename = '{}.{}'.format(name, self.suffix)
if import_filename_tpl:
self.import_filename = import_filename_tpl.format(self)
# There may have been more outputs added by the time we get here, so
Expand Down Expand Up @@ -2418,6 +2438,9 @@ def process_kwargs(self, kwargs):
# Visual Studio module-definitions file
self.process_vs_module_defs_kw(kwargs)

# OS/2 uses a 8.3 name for a DLL
self.shortname = kwargs.get('shortname')

rust_abi = kwargs.get('rust_abi')
rust_crate_type = kwargs.get('rust_crate_type')
if rust_crate_type:
Expand Down
2 changes: 2 additions & 0 deletions mesonbuild/compilers/compilers.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,8 @@ def are_asserts_disabled(options: KeyedOptionDictType) -> bool:

def get_base_compile_args(options: 'KeyedOptionDictType', compiler: 'Compiler', env: 'Environment') -> T.List[str]:
args: T.List[str] = []
if mesonlib.is_os2() and options[OptionKey('emxomf')].value:
args += ['-Zomf']
try:
if options.get_value(OptionKey('b_lto')):
args.extend(compiler.get_lto_compile_args(
Expand Down
6 changes: 6 additions & 0 deletions mesonbuild/compilers/detect.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
defaults['cuda_static_linker'] = ['nvlink']
defaults['gcc_static_linker'] = ['gcc-ar']
defaults['clang_static_linker'] = ['llvm-ar']
defaults['emxomf_static_linker'] = ['emxomfar']
defaults['nasm'] = ['nasm', 'yasm']


Expand Down Expand Up @@ -156,6 +157,7 @@ def _handle_exceptions(
def detect_static_linker(env: 'Environment', compiler: Compiler) -> StaticLinker:
from . import d
from ..linkers import linkers
from ..options import OptionKey
linker = env.lookup_binary_entry(compiler.for_machine, 'ar')
if linker is not None:
trials = [linker]
Expand All @@ -165,6 +167,8 @@ def detect_static_linker(env: 'Environment', compiler: Compiler) -> StaticLinker
trials = [defaults['cuda_static_linker']] + default_linkers
elif compiler.get_argument_syntax() == 'msvc':
trials = [defaults['vs_static_linker'], defaults['clang_cl_static_linker']]
elif env.machines[compiler.for_machine].is_os2() and env.coredata.get_option(OptionKey('emxomf')):
trials = [defaults['emxomf_static_linker']] + default_linkers
elif compiler.id == 'gcc':
# Use gcc-ar if available; needed for LTO
trials = [defaults['gcc_static_linker']] + default_linkers
Expand Down Expand Up @@ -251,6 +255,8 @@ def detect_static_linker(env: 'Environment', compiler: Compiler) -> StaticLinker
return linkers.AIXArLinker(linker)
if p.returncode == 1 and err.startswith('ar: bad option: --'): # Solaris
return linkers.ArLinker(compiler.for_machine, linker)
if p.returncode == 1 and err.startswith('emxomfar'):
return linkers.EmxomfArLinker(compiler.for_machine, linker)
_handle_exceptions(popen_exceptions, trials, 'linker')
raise EnvironmentException('Unreachable code (exception to make mypy happy)')

Expand Down
28 changes: 24 additions & 4 deletions mesonbuild/compilers/mixins/clike.py
Original file line number Diff line number Diff line change
Expand Up @@ -990,9 +990,17 @@ def symbols_have_underscore_prefix(self, env: 'Environment') -> bool:

def _get_patterns(self, env: 'Environment', prefixes: T.List[str], suffixes: T.List[str], shared: bool = False) -> T.List[str]:
patterns: T.List[str] = []
for p in prefixes:
for s in suffixes:
patterns.append(p + '{}.' + s)
# On OS/2, search order for shared libs is
# 1. libfoo_dll.a
# 2. foo_dll.a
# 3. libfoo.a
# 4. foo.a
# 5. foo.dll
# For static libs, `_s' is used instead of `_dll'.
for s in suffixes:
dot = '' if env.machines[self.for_machine].is_os2() and s.startswith(('_dll.', '_s.')) else '.'
for p in prefixes:
patterns.append(p + '{}' + dot + s)
if shared and env.machines[self.for_machine].is_openbsd():
# Shared libraries on OpenBSD can be named libfoo.so.X.Y:
# https://www.openbsd.org/faq/ports/specialtopics.html#SharedLibs
Expand All @@ -1015,7 +1023,9 @@ def get_library_naming(self, env: 'Environment', libtype: LibType, strict: bool
# people depend on it. Also, some people use prebuilt `foo.so` instead
# of `libfoo.so` for unknown reasons, and may also want to create
# `foo.so` by setting name_prefix to ''
if strict and not isinstance(self, VisualStudioLikeCompiler): # lib prefix is not usually used with msvc
# lib prefix is not usually used with msvc and OS/2
if strict and not isinstance(self, VisualStudioLikeCompiler) \
and not env.machines[self.for_machine].is_os2():
prefixes = ['lib']
else:
prefixes = ['lib', '']
Expand All @@ -1038,6 +1048,14 @@ def get_library_naming(self, env: 'Environment', libtype: LibType, strict: bool
# TI C28x compilers can use both extensions for static or dynamic libs.
stlibext = ['a', 'lib']
shlibext = ['dll', 'so']
elif env.machines[self.for_machine].is_os2():
from ...options import OptionKey
if env.coredata.get_option(OptionKey('emxomf')):
stlibext = ['_s.lib', '_s.a', 'lib', 'a']
shlibext = ['_dll.lib', '_dll.a', 'lib', 'a', 'dll']
else:
stlibext = ['_s.a', 'a']
shlibext = ['_dll.a', 'a', 'dll']
else:
# Linux/BSDs
shlibext = ['so']
Expand Down Expand Up @@ -1255,6 +1273,8 @@ def thread_flags(self, env: 'Environment') -> T.List[str]:
host_m = env.machines[self.for_machine]
if host_m.is_haiku() or host_m.is_darwin():
return []
if host_m.is_os2():
return ['-lpthread']
return ['-pthread']

def linker_to_compiler_args(self, args: T.List[str]) -> T.List[str]:
Expand Down
4 changes: 2 additions & 2 deletions mesonbuild/compilers/mixins/gnu.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,8 +373,8 @@ def __init__(self) -> None:
self.can_compile_suffixes.add('sx')

def get_pic_args(self) -> T.List[str]:
if self.info.is_windows() or self.info.is_cygwin() or self.info.is_darwin():
return [] # On Window and OS X, pic is always on.
if self.info.is_windows() or self.info.is_cygwin() or self.info.is_darwin() or self.info.is_os2():
return [] # On Window, OS X and OS/2, pic is always on.
return ['-fPIC']

def get_pie_args(self) -> T.List[str]:
Expand Down
8 changes: 7 additions & 1 deletion mesonbuild/envconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,11 +367,17 @@ def is_irix(self) -> bool:
"""Machine is IRIX?"""
return self.system.startswith('irix')

def is_os2(self) -> bool:
"""
Machine is OS/2?
"""
return self.system == 'os/2'

# Various prefixes and suffixes for import libraries, shared libraries,
# static libraries, and executables.
# Versioning is added to these names in the backends as-needed.
def get_exe_suffix(self) -> str:
if self.is_windows() or self.is_cygwin():
if self.is_windows() or self.is_cygwin() or self.is_os2():
return 'exe'
else:
return ''
Expand Down
1 change: 1 addition & 0 deletions mesonbuild/interpreter/type_checking.py
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,7 @@ def _convert_darwin_versions(val: T.List[T.Union[str, int]]) -> T.Optional[T.Tup
_DARWIN_VERSIONS_KW,
KwargInfo('soversion', (str, int, NoneType), convertor=lambda x: str(x) if x is not None else None),
KwargInfo('version', (str, NoneType), validator=_validate_shlib_version),
KwargInfo('shortname', (str, NoneType), since='1.8'),
]

# The total list of arguments used by SharedLibrary
Expand Down
3 changes: 3 additions & 0 deletions mesonbuild/linkers/detect.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
defaults['cuda_static_linker'] = ['nvlink']
defaults['gcc_static_linker'] = ['gcc-ar']
defaults['clang_static_linker'] = ['llvm-ar']
defaults['emxomf_static_linker'] = ['emxomfar']

def __failed_to_detect_linker(compiler: T.List[str], args: T.List[str], stdout: str, stderr: str) -> 'T.NoReturn':
msg = 'Unable to detect linker for compiler `{}`\nstdout: {}\nstderr: {}'.format(
Expand Down Expand Up @@ -242,6 +243,8 @@ def guess_nix_linker(env: 'Environment', compiler: T.List[str], comp_class: T.Ty
else:
__failed_to_detect_linker(compiler, check_args, o, e)
linker = linkers.AppleDynamicLinker(compiler, for_machine, comp_class.LINKER_PREFIX, override, version=v)
elif 'ld.exe: unrecognized option' in e or 'emxomfld: invalid option' in e:
linker = linkers.OS2DynamicLinker(compiler, for_machine, comp_class.LINKER_PREFIX, override, version='none')
else:
__failed_to_detect_linker(compiler, check_args, o, e)
return linker
33 changes: 32 additions & 1 deletion mesonbuild/linkers/linkers.py
Original file line number Diff line number Diff line change
Expand Up @@ -387,8 +387,10 @@ def get_std_link_args(self, env: 'Environment', is_thin: bool) -> T.List[str]:
# on Mac OS X, Solaris, or illumos, so don't build them on those OSes.
# OS X ld rejects with: "file built for unknown-unsupported file format"
# illumos/Solaris ld rejects with: "unknown file type"
# OS/2 ld rejects with: "malformed input file (not rel or archive)"
if is_thin and not env.machines[self.for_machine].is_darwin() \
and not env.machines[self.for_machine].is_sunos():
and not env.machines[self.for_machine].is_sunos() \
and not env.machines[self.for_machine].is_os2():
return self.std_thin_args
else:
return self.std_args
Expand Down Expand Up @@ -544,6 +546,13 @@ def get_output_args(self, target: str) -> T.List[str]:
def get_linker_always_args(self) -> T.List[str]:
return ['-r']


class EmxomfArLinker(ArLinker):
id = 'emxomfar'

def get_std_link_args(self, env: 'Environment', is_thin: bool) -> T.List[str]:
return ['cr']

def prepare_rpaths(raw_rpaths: T.Tuple[str, ...], build_dir: str, from_dir: str) -> T.List[str]:
# The rpaths we write must be relative if they point to the build dir,
# because otherwise they have different length depending on the build
Expand Down Expand Up @@ -1734,3 +1743,25 @@ def get_link_whole_for(self, args: T.List[str]) -> T.List[str]:
for a in args:
l.extend(self._apply_prefix('-Wl--whole-archive=' + a))
return l


class OS2DynamicLinker(PosixDynamicLinkerMixin, DynamicLinker):
"""ld and emxomfld"""

id = 'ld.os2'

def get_allow_undefined_args(self) -> T.List[str]:
return []

def thread_flags(self, env: 'Environment') -> T.List[str]:
return ['-lpthread']

def get_std_shared_lib_args(self) -> T.List[str]:
return ['-Zdll']

def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str,
suffix: str, soversion: str, darwin_versions: T.Tuple[str, str]) -> T.List[str]:
return []

def get_always_args(self) -> T.List[str]:
return ['-Zomf']
2 changes: 2 additions & 0 deletions mesonbuild/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ class ArgparseKWs(TypedDict, total=False):
'pkg_config_path',
'cmake_prefix_path',
'vsenv',
'emxomf',
}

@total_ordering
Expand Down Expand Up @@ -647,6 +648,7 @@ def add_to_argparse(self, name: str, parser: argparse.ArgumentParser, help_suffi
(OptionKey('wrap_mode'), BuiltinOption(UserComboOption, 'Wrap mode', 'default', choices=['default', 'nofallback', 'nodownload', 'forcefallback', 'nopromote'])),
(OptionKey('force_fallback_for'), BuiltinOption(UserArrayOption, 'Force fallback for those subprojects', [])),
(OptionKey('vsenv'), BuiltinOption(UserBooleanOption, 'Activate Visual Studio environment', False, readonly=True)),
(OptionKey('emxomf'), BuiltinOption(UserBooleanOption, "Whether to use OMF format on OS/2", False)),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how I feel about configuring the object format from a global option, I need to think more about this, but it might be more appropriate as a compiler option.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, it would be coolI if possible to change the object format without modifying meson.build file. And it's possible to prepare compiler options depending on the global option.


# Pkgconfig module
(OptionKey('pkgconfig.relocatable'),
Expand Down
5 changes: 5 additions & 0 deletions mesonbuild/programs.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,11 @@ def _search(self, name: str, search_dirs: T.List[T.Optional[str]], exclude_paths
paths = OrderedSet(path.split(os.pathsep)).difference(exclude_paths)
path = os.pathsep.join(paths)
command = shutil.which(name, path=path)
if not command and mesonlib.is_os2():
for ext in ['exe', 'cmd']:
command = shutil.which(f'{name}.{ext}', path=path)
if command:
return [command]
if mesonlib.is_windows():
return self._search_windows_special_cases(name, command, exclude_paths)
# On UNIX-like platforms, shutil.which() is enough to find
Expand Down
4 changes: 4 additions & 0 deletions mesonbuild/utils/universal.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ class _VerPickleLoadable(Protocol):
'is_linux',
'is_netbsd',
'is_openbsd',
'is_os2',
'is_osx',
'is_qnx',
'is_sunos',
Expand Down Expand Up @@ -691,6 +692,9 @@ def is_qnx() -> bool:
def is_aix() -> bool:
return platform.system().lower() == 'aix'

def is_os2() -> bool:
return platform.system().lower() == 'os/2'

@lru_cache(maxsize=None)
def darwin_get_object_archs(objpath: str) -> 'ImmutableListProtocol[str]':
'''
Expand Down
Loading