diff options
33 files changed, 454 insertions, 114 deletions
diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index 802002b..cb8347a 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -1313,7 +1313,11 @@ The content is a series of key/value pairs grouped into sections. If the section keyword argument is omitted, those key/value pairs are implicitly grouped into a section with no title. key/value pairs can optionally be grouped into a dictionary, but keep in mind that dictionaries does not guarantee ordering. `key` must be string, -`value` can only be integer, boolean, string, or a list of those. +`value` can be: + +- an integer, boolean or string +- *since 0.57.0* an external program or a dependency +- a list of those. `summary()` can be called multiple times as long as the same section/key pair doesn't appear twice. All sections will be collected and printed at diff --git a/docs/markdown/Syntax.md b/docs/markdown/Syntax.md index 65cb70f..4f17ac8 100644 --- a/docs/markdown/Syntax.md +++ b/docs/markdown/Syntax.md @@ -657,7 +657,7 @@ equality_expression: relational_expression | (equality_expression equality_opera equality_operator: "==" | "!=" expression: assignment_expression expression_list: expression ("," expression)* -expression_statememt: expression +expression_statement: expression function_expression: id_expression "(" [argument_list] ")" hex_literal: "0x" HEX_NUMBER HEX_NUMBER: /[a-fA-F0-9]+/ diff --git a/docs/markdown/snippets/summary_prog_dep.md b/docs/markdown/snippets/summary_prog_dep.md new file mode 100644 index 0000000..6f0262f --- /dev/null +++ b/docs/markdown/snippets/summary_prog_dep.md @@ -0,0 +1,4 @@ +## `summary()` accepts external programs or dependencies + +External program objects and dependency objects can be passed to +`summary()` as the value to be printed. diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index ced1d7d..8143941 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -14,6 +14,7 @@ from collections import OrderedDict from functools import lru_cache +from itertools import chain from pathlib import Path import enum import json @@ -44,6 +45,11 @@ if T.TYPE_CHECKING: InstallType = T.List[T.Tuple[str, str, T.Optional['FileMode']]] InstallSubdirsType = T.List[T.Tuple[str, str, T.Optional['FileMode'], T.Tuple[T.Set[str], T.Set[str]]]] +# Languages that can mix with C or C++ but don't support unity builds yet +# because the syntax we use for unity builds is specific to C/++/ObjC/++. +# Assembly files cannot be unitified and neither can LLVM IR files +LANGS_CANT_UNITY = ('d', 'fortran') + class TestProtocol(enum.Enum): EXITCODE = 0 @@ -636,6 +642,9 @@ class Backend: unity_size = self.get_option_for_target(OptionKey('unity_size'), extobj.target) for comp, srcs in compsrcs.items(): + if comp.language in LANGS_CANT_UNITY: + sources += srcs + continue for i in range(len(srcs) // unity_size + 1): osrc = self.get_unity_source_file(extobj.target, comp.get_default_suffix(), i) @@ -767,7 +776,7 @@ class Backend: # pkg-config puts the thread flags itself via `Cflags:` # Fortran requires extra include directives. if compiler.language == 'fortran': - for lt in target.link_targets: + for lt in chain(target.link_targets, target.link_whole_targets): priv_dir = self.get_target_private_dir(lt) commands += compiler.get_include_args(priv_dir, False) return commands diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 6c524a0..dd70ae0 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -626,19 +626,14 @@ int dummy; srcs[f] = s return srcs - # Languages that can mix with C or C++ but don't support unity builds yet - # because the syntax we use for unity builds is specific to C/++/ObjC/++. - # Assembly files cannot be unitified and neither can LLVM IR files - langs_cant_unity = ('d', 'fortran') - def get_target_source_can_unity(self, target, source): if isinstance(source, File): source = source.fname if self.environment.is_llvm_ir(source) or \ self.environment.is_assembly(source): return False - suffix = os.path.splitext(source)[1][1:] - for lang in self.langs_cant_unity: + suffix = os.path.splitext(source)[1][1:].lower() + for lang in backends.LANGS_CANT_UNITY: if lang not in target.compilers: continue if suffix in target.compilers[lang].file_suffixes: @@ -762,7 +757,7 @@ int dummy; if is_unity: # Warn about incompatible sources if a unity build is enabled langs = set(target.compilers.keys()) - langs_cant = langs.intersection(self.langs_cant_unity) + langs_cant = langs.intersection(backends.LANGS_CANT_UNITY) if langs_cant: langs_are = langs = ', '.join(langs_cant).upper() langs_are += ' are' if len(langs_cant) > 1 else ' is' @@ -918,7 +913,7 @@ int dummy; all_suffixes = set(compilers.lang_suffixes['cpp']) | set(compilers.lang_suffixes['fortran']) selected_sources = [] for source in compiled_sources: - ext = os.path.splitext(source)[1][1:] + ext = os.path.splitext(source)[1][1:].lower() if ext in all_suffixes: selected_sources.append(source) return selected_sources @@ -2602,7 +2597,10 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) def get_fortran_orderdeps(self, target, compiler): if compiler.language != 'fortran': return [] - return [os.path.join(self.get_target_dir(lt), lt.get_filename()) for lt in target.link_targets] + return [ + os.path.join(self.get_target_dir(lt), lt.get_filename()) + for lt in itertools.chain(target.link_targets, target.link_whole_targets) + ] def generate_msvc_pch_command(self, target, compiler, pch): header = pch[0] @@ -3026,14 +3024,14 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) def generate_gcov_clean(self): gcno_elem = NinjaBuildElement(self.all_outputs, 'meson-clean-gcno', 'CUSTOM_COMMAND', 'PHONY') - gcno_elem.add_item('COMMAND', mesonlib.meson_command + ['--internal', 'delwithsuffix', '.', 'gcno']) + gcno_elem.add_item('COMMAND', mesonlib.get_meson_command() + ['--internal', 'delwithsuffix', '.', 'gcno']) gcno_elem.add_item('description', 'Deleting gcno files') self.add_build(gcno_elem) # Alias that runs the target defined above self.create_target_alias('meson-clean-gcno') gcda_elem = NinjaBuildElement(self.all_outputs, 'meson-clean-gcda', 'CUSTOM_COMMAND', 'PHONY') - gcda_elem.add_item('COMMAND', mesonlib.meson_command + ['--internal', 'delwithsuffix', '.', 'gcda']) + gcda_elem.add_item('COMMAND', mesonlib.get_meson_command() + ['--internal', 'delwithsuffix', '.', 'gcda']) gcda_elem.add_item('description', 'Deleting gcda files') self.add_build(gcda_elem) # Alias that runs the target defined above diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index 6e070a7..e4886ba 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -176,7 +176,11 @@ class Vs2010Backend(backends.Backend): # x86 self.platform = 'Win32' elif target_machine == 'aarch64' or target_machine == 'arm64': - self.platform = 'arm64' + target_cpu = self.interpreter.builtin['target_machine'].cpu_method(None, None) + if target_cpu == 'arm64ec': + self.platform = 'arm64ec' + else: + self.platform = 'arm64' elif 'arm' in target_machine.lower(): self.platform = 'ARM' else: @@ -1218,6 +1222,8 @@ class Vs2010Backend(backends.Backend): targetmachine.text = 'MachineARM' elif targetplatform == 'arm64': targetmachine.text = 'MachineARM64' + elif targetplatform == 'arm64ec': + targetmachine.text = 'MachineARM64EC' else: raise MesonException('Unsupported Visual Studio target machine: ' + targetplatform) # /nologo diff --git a/mesonbuild/backend/xcodebackend.py b/mesonbuild/backend/xcodebackend.py index 0e39c65..6f14cbb 100644 --- a/mesonbuild/backend/xcodebackend.py +++ b/mesonbuild/backend/xcodebackend.py @@ -578,7 +578,7 @@ class XCodeBackend(backends.Backend): self.write_line(');') self.write_line('runOnlyForDeploymentPostprocessing = 0;') self.write_line('shellPath = /bin/sh;') - cmd = mesonlib.meson_command + ['test', test_data, '-C', self.environment.get_build_dir()] + cmd = mesonlib.get_meson_command() + ['test', test_data, '-C', self.environment.get_build_dir()] cmdstr = ' '.join(["'%s'" % i for i in cmd]) self.write_line('shellScript = "%s";' % cmdstr) self.write_line('showEnvVarsInLog = 0;') diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index 782b7c2..abb4983 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -1322,7 +1322,7 @@ class CMakeInterpreter: # Generate the command list command = [] # type: T.List[T.Union[str, IdNode, IndexNode]] - command += mesonlib.meson_command + command += mesonlib.get_meson_command() command += ['--internal', 'cmake_run_ctgt'] command += ['-o', '@OUTPUT@'] if tgt.original_outputs: diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index c11319b..0a2d478 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -16,7 +16,8 @@ import os.path import typing as T from .. import coredata -from ..mesonlib import MachineChoice, MesonException, mlog, version_compare, OptionKey +from .. import mlog +from ..mesonlib import MachineChoice, MesonException, version_compare, OptionKey from .c_function_attributes import C_FUNC_ATTRIBUTES from .mixins.clike import CLikeCompiler from .mixins.ccrx import CcrxCompiler diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index e72f346..cd77b4b 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -134,6 +134,13 @@ class Dependency: def is_built(self) -> bool: return False + def summary_value(self) -> T.Union[str, mlog.AnsiDecorator, mlog.AnsiText]: + if not self.found(): + return mlog.red('NO') + if not self.version: + return mlog.green('YES') + return mlog.AnsiText(mlog.green('YES'), ' ', mlog.cyan(self.version)) + def get_compile_args(self) -> T.List[str]: if self.include_type == 'system': converted = [] @@ -263,6 +270,11 @@ class InternalDependency(Dependency): setattr(result, k, copy.deepcopy(v, memo)) return result + def summary_value(self) -> mlog.AnsiDecorator: + # Omit the version. Most of the time it will be just the project + # version, which is uninteresting in the summary. + return mlog.green('YES') + def is_built(self) -> bool: if self.sources or self.libraries or self.whole_libraries: return True @@ -1888,6 +1900,11 @@ class ExternalProgram: else: mlog.log('Program', mlog.bold(name), 'found:', mlog.red('NO')) + def summary_value(self) -> T.Union[str, mlog.AnsiDecorator]: + if not self.found(): + return mlog.red('NO') + return self.path + def __repr__(self) -> str: r = '<{} {!r} -> {!r}>' return r.format(self.__class__.__name__, self.name, self.command) diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index 1a7f3f1..219b0f2 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -867,7 +867,7 @@ class Environment: # re-initialized with project options by the interpreter during # build file parsing. # meson_command is used by the regenchecker script, which runs meson - self.coredata = coredata.CoreData(options, self.scratch_dir, mesonlib.meson_command) + self.coredata = coredata.CoreData(options, self.scratch_dir, mesonlib.get_meson_command()) self.first_invocation = True def is_cross_build(self, when_building_for: MachineChoice = MachineChoice.HOST) -> bool: @@ -887,7 +887,7 @@ class Environment: return self.coredata def get_build_command(self, unbuffered=False): - cmd = mesonlib.meson_command[:] + cmd = mesonlib.get_meson_command().copy() if unbuffered and 'python' in os.path.basename(cmd[0]): cmd.insert(1, '-u') return cmd diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index c7049c3..a3fa050 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -1854,7 +1854,7 @@ class Summary: self.sections = collections.defaultdict(dict) self.max_key_len = 0 - def add_section(self, section, values, kwargs): + def add_section(self, section, values, kwargs, subproject): bool_yn = kwargs.get('bool_yn', False) if not isinstance(bool_yn, bool): raise InterpreterException('bool_yn keyword argument must be boolean') @@ -1866,24 +1866,20 @@ class Summary: raise InterpreterException('Summary section {!r} already have key {!r}'.format(section, k)) formatted_values = [] for i in listify(v): - if not isinstance(i, (str, int)): - m = 'Summary value in section {!r}, key {!r}, must be string, integer or boolean' - raise InterpreterException(m.format(section, k)) - if bool_yn and isinstance(i, bool): + i = unholder(i) + if isinstance(i, bool) and bool_yn: formatted_values.append(mlog.green('YES') if i else mlog.red('NO')) - else: + elif isinstance(i, (str, int, bool)): formatted_values.append(str(i)) + elif isinstance(i, (ExternalProgram, Dependency)): + FeatureNew.single_use('dependency or external program in summary', '0.57.0', subproject) + formatted_values.append(i.summary_value()) + else: + m = 'Summary value in section {!r}, key {!r}, must be string, integer, boolean, dependency or external program' + raise InterpreterException(m.format(section, k)) self.sections[section][k] = (formatted_values, list_sep) self.max_key_len = max(self.max_key_len, len(k)) - def text_len(self, v): - if isinstance(v, str): - return len(v) - elif isinstance(v, mlog.AnsiDecorator): - return len(v.text) - else: - raise RuntimeError('Expecting only strings or AnsiDecorator') - def dump(self): mlog.log(self.project_name, mlog.normal_cyan(self.project_version)) for section, values in self.sections.items(): @@ -1909,7 +1905,7 @@ class Summary: line_len = indent lines_sep = list_sep.rstrip() + lines_sep for v in arr: - v_len = self.text_len(v) + len(list_sep) + v_len = len(v) + len(list_sep) if line and line_len + v_len > max_len: mlog.log(*line, sep=list_sep, end=lines_sep) line_len = indent @@ -3292,7 +3288,7 @@ external dependencies (including libraries) must go to "dependencies".''') def summary_impl(self, section, values, kwargs): if self.subproject not in self.summary: self.summary[self.subproject] = Summary(self.active_projectname, self.project_version) - self.summary[self.subproject].add_section(section, values, kwargs) + self.summary[self.subproject].add_section(section, values, kwargs, self.subproject) def _print_summary(self): # Add automatic 'Supbrojects' section in main project. diff --git a/mesonbuild/mdist.py b/mesonbuild/mdist.py index 3ca13e5..5471505 100644 --- a/mesonbuild/mdist.py +++ b/mesonbuild/mdist.py @@ -245,7 +245,7 @@ def run(options): b = build.load(options.wd) # This import must be load delayed, otherwise it will get the default # value of None. - from mesonbuild.mesonlib import meson_command + from mesonbuild.mesonlib import get_meson_command src_root = b.environment.source_dir bld_root = b.environment.build_dir priv_dir = os.path.join(bld_root, 'meson-private') @@ -279,7 +279,7 @@ def run(options): rc = 0 if not options.no_tests: # Check only one. - rc = check_dist(names[0], meson_command, extra_meson_args, bld_root, priv_dir) + rc = check_dist(names[0], get_meson_command(), extra_meson_args, bld_root, priv_dir) if rc == 0: for name in names: create_hash(name) diff --git a/mesonbuild/mesonlib/__init__.py b/mesonbuild/mesonlib/__init__.py new file mode 100644 index 0000000..5b646b5 --- /dev/null +++ b/mesonbuild/mesonlib/__init__.py @@ -0,0 +1,30 @@ +# SPDX-license-identifier: Apache-2.0 +# Copyright 2012-2021 The Meson development team +# Copyright © 2021 Intel Corporation + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Helper functions and classes.""" + +import os + +from .universal import * + +# Here we import either the posix implementations, the windows implementations, +# or a generic no-op implementation +if os.name == 'posix': + from .posix import * +elif os.name == 'nt': + from .win32 import * +else: + from .platform import * diff --git a/mesonbuild/mesonlib/platform.py b/mesonbuild/mesonlib/platform.py new file mode 100644 index 0000000..cdd42b1 --- /dev/null +++ b/mesonbuild/mesonlib/platform.py @@ -0,0 +1,37 @@ +# SPDX-license-identifier: Apache-2.0 +# Copyright 2012-2021 The Meson development team +# Copyright © 2021 Intel Corporation + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""base classes providing no-op functionality..""" + +import os +import typing as T + +from .. import mlog + +__all__ = ['BuildDirLock'] + +# This needs to be inheritted by the specific implementations to make type +# checking happy +class BuildDirLock: + + def __init__(self, builddir: str) -> None: + self.lockfilename = os.path.join(builddir, 'meson-private/meson.lock') + + def __enter__(self) -> None: + mlog.debug('Calling ther no-op version of BuildDirLock') + + def __exit__(self, *args: T.Any) -> None: + pass diff --git a/mesonbuild/mesonlib/posix.py b/mesonbuild/mesonlib/posix.py new file mode 100644 index 0000000..1d8ba8c --- /dev/null +++ b/mesonbuild/mesonlib/posix.py @@ -0,0 +1,39 @@ +# SPDX-license-identifier: Apache-2.0 +# Copyright 2012-2021 The Meson development team +# Copyright © 2021 Intel Corporation + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Posix specific implementations of mesonlib functionality.""" + +import fcntl +import typing as T + +from .universal import MesonException +from .platform import BuildDirLock as BuildDirLockBase + +__all__ = ['BuildDirLock'] + +class BuildDirLock(BuildDirLockBase): + + def __enter__(self) -> None: + self.lockfile = open(self.lockfilename, 'w') + try: + fcntl.flock(self.lockfile, fcntl.LOCK_EX | fcntl.LOCK_NB) + except (BlockingIOError, PermissionError): + self.lockfile.close() + raise MesonException('Some other Meson process is already using this build directory. Exiting.') + + def __exit__(self, *args: T.Any) -> None: + fcntl.flock(self.lockfile, fcntl.LOCK_UN) + self.lockfile.close() diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib/universal.py index ef48ec2..c16f165 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib/universal.py @@ -30,34 +30,117 @@ import textwrap from mesonbuild import mlog if T.TYPE_CHECKING: - from .build import ConfigurationData - from .coredata import KeyedOptionDictType, UserOption - from .compilers.compilers import CompilerType - from .interpreterbase import ObjectHolder + from ..build import ConfigurationData + from ..coredata import KeyedOptionDictType, UserOption + from ..compilers.compilers import CompilerType + from ..interpreterbase import ObjectHolder - FileOrString = T.Union['File', str] +FileOrString = T.Union['File', str] _T = T.TypeVar('_T') _U = T.TypeVar('_U') -have_fcntl = False -have_msvcrt = False +__all__ = [ + 'GIT', + 'an_unpicklable_object', + 'python_command', + 'project_meson_versions', + 'File', + 'FileMode', + 'GitException', + 'LibType', + 'MachineChoice', + 'MesonException', + 'EnvironmentException', + 'FileOrString', + 'GitException', + 'OptionKey', + 'dump_conf_header', + 'OptionOverrideProxy', + 'OptionProxy', + 'OptionType', + 'OrderedSet', + 'PerMachine', + 'PerMachineDefaultable', + 'PerThreeMachine', + 'PerThreeMachineDefaultable', + 'ProgressBar', + 'TemporaryDirectoryWinProof', + 'Version', + 'check_direntry_issues', + 'classify_unity_sources', + 'current_vs_supports_modules', + 'darwin_get_object_archs', + 'default_libdir', + 'default_libexecdir', + 'default_prefix', + 'detect_subprojects', + 'detect_vcs', + 'do_conf_file', + 'do_conf_str', + 'do_define', + 'do_replacement', + 'exe_exists', + 'expand_arguments', + 'extract_as_list', + 'get_compiler_for_source', + 'get_filenames_templates_dict', + 'get_library_dirs', + 'get_variable_regex', + 'get_wine_shortpath', + 'git', + 'has_path_sep', + 'is_aix', + 'is_android', + 'is_ascii_string', + 'is_cygwin', + 'is_debianlike', + 'is_dragonflybsd', + 'is_freebsd', + 'is_haiku', + 'is_hurd', + 'is_irix', + 'is_linux', + 'is_netbsd', + 'is_openbsd', + 'is_osx', + 'is_qnx', + 'is_sunos', + 'is_windows', + 'iter_regexin_iter', + 'join_args', + 'listify', + 'partition', + 'path_is_in_root', + 'Popen_safe', + 'quiet_git', + 'quote_arg', + 'relative_to_if_possible', + 'relpath', + 'replace_if_different', + 'run_once', + 'get_meson_command', + 'set_meson_command', + 'split_args', + 'stringlistify', + 'substitute_values', + 'substring_is_in_list', + 'typeslistify', + 'unholder', + 'verbose_git', + 'version_compare', + 'version_compare_condition_with_min', + 'version_compare_many', + 'windows_proof_rm', + 'windows_proof_rmtree', +] + + # TODO: this is such a hack, this really should be either in coredata or in the # interpreter # {subproject: project_meson_version} project_meson_versions = collections.defaultdict(str) # type: T.DefaultDict[str, str] -try: - import fcntl - have_fcntl = True -except Exception: - pass - -try: - import msvcrt - have_msvcrt = True -except Exception: - pass from glob import glob @@ -66,7 +149,7 @@ if os.path.basename(sys.executable) == 'meson.exe': python_command = [sys.executable, 'runpython'] else: python_command = [sys.executable] -meson_command = None +_meson_command = None class MesonException(Exception): '''Exceptions thrown by Meson''' @@ -117,20 +200,24 @@ def verbose_git(cmd: T.List[str], workingdir: str, check: bool = False) -> bool: def set_meson_command(mainfile: str) -> None: global python_command - global meson_command + global _meson_command # On UNIX-like systems `meson` is a Python script # On Windows `meson` and `meson.exe` are wrapper exes if not mainfile.endswith('.py'): - meson_command = [mainfile] + _meson_command = [mainfile] elif os.path.isabs(mainfile) and mainfile.endswith('mesonmain.py'): # Can't actually run meson with an absolute path to mesonmain.py, it must be run as -m mesonbuild.mesonmain - meson_command = python_command + ['-m', 'mesonbuild.mesonmain'] + _meson_command = python_command + ['-m', 'mesonbuild.mesonmain'] else: # Either run uninstalled, or full path to meson-script.py - meson_command = python_command + [mainfile] + _meson_command = python_command + [mainfile] # We print this value for unit tests. if 'MESON_COMMAND_TESTS' in os.environ: - mlog.log('meson_command is {!r}'.format(meson_command)) + mlog.log('meson_command is {!r}'.format(_meson_command)) + + +def get_meson_command() -> T.Optional[T.List[str]]: + return _meson_command def is_ascii_string(astring: T.Union[str, bytes]) -> bool: @@ -1564,29 +1651,6 @@ class OrderedSet(T.MutableSet[_T]): def difference(self, set_: T.Union[T.Set[_T], 'OrderedSet[_T]']) -> 'OrderedSet[_T]': return type(self)(e for e in self if e not in set_) -class BuildDirLock: - - def __init__(self, builddir: str) -> None: - self.lockfilename = os.path.join(builddir, 'meson-private/meson.lock') - - def __enter__(self) -> None: - self.lockfile = open(self.lockfilename, 'w') - try: - if have_fcntl: - fcntl.flock(self.lockfile, fcntl.LOCK_EX | fcntl.LOCK_NB) - elif have_msvcrt: - msvcrt.locking(self.lockfile.fileno(), msvcrt.LK_NBLCK, 1) - except (BlockingIOError, PermissionError): - self.lockfile.close() - raise MesonException('Some other Meson process is already using this build directory. Exiting.') - - def __exit__(self, *args: T.Any) -> None: - if have_fcntl: - fcntl.flock(self.lockfile, fcntl.LOCK_UN) - elif have_msvcrt: - msvcrt.locking(self.lockfile.fileno(), msvcrt.LK_UNLCK, 1) - self.lockfile.close() - def relpath(path: str, start: str) -> str: # On Windows a relative path can't be evaluated for paths on two different # drives (i.e. c:\foo and f:\bar). The only thing left to do is to use the @@ -1965,7 +2029,7 @@ class OptionKey: raw3 = raw2 for_machine = MachineChoice.HOST - from .compilers import all_languages + from ..compilers import all_languages if any(raw3.startswith(f'{l}_') for l in all_languages): lang, opt = raw3.split('_', 1) else: @@ -2024,4 +2088,4 @@ class OptionKey: def is_base(self) -> bool: """Convenience method to check if this is a base option.""" - return self.type is OptionType.BASE
\ No newline at end of file + return self.type is OptionType.BASE diff --git a/mesonbuild/mesonlib/win32.py b/mesonbuild/mesonlib/win32.py new file mode 100644 index 0000000..0919ef7 --- /dev/null +++ b/mesonbuild/mesonlib/win32.py @@ -0,0 +1,39 @@ +# SPDX-license-identifier: Apache-2.0 +# Copyright 2012-2021 The Meson development team +# Copyright © 2021 Intel Corporation + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Windows specific implementations of mesonlib functionality.""" + +import msvcrt +import typing as T + +from .universal import MesonException +from .platform import BuildDirLock as BuildDirLockBase + +__all__ = ['BuildDirLock'] + +class BuildDirLock(BuildDirLockBase): + + def __enter__(self) -> None: + self.lockfile = open(self.lockfilename, 'w') + try: + msvcrt.locking(self.lockfile.fileno(), msvcrt.LK_NBLCK, 1) + except (BlockingIOError, PermissionError): + self.lockfile.close() + raise MesonException('Some other Meson process is already using this build directory. Exiting.') + + def __exit__(self, *args: T.Any) -> None: + msvcrt.locking(self.lockfile.fileno(), msvcrt.LK_UNLCK, 1) + self.lockfile.close() diff --git a/mesonbuild/minit.py b/mesonbuild/minit.py index 4a38313..55e716c 100644 --- a/mesonbuild/minit.py +++ b/mesonbuild/minit.py @@ -174,7 +174,7 @@ def run(options: 'argparse.Namespace') -> int: print('Build directory already exists, deleting it.') shutil.rmtree(options.builddir) print('Building...') - cmd = mesonlib.meson_command + [options.builddir] + cmd = mesonlib.get_meson_command() + [options.builddir] ret = subprocess.run(cmd) if ret.returncode: raise SystemExit diff --git a/mesonbuild/mlog.py b/mesonbuild/mlog.py index 0cffba7..20794df 100644 --- a/mesonbuild/mlog.py +++ b/mesonbuild/mlog.py @@ -130,6 +130,24 @@ class AnsiDecorator: text = '"{}"'.format(text) return text + def __len__(self) -> int: + return len(self.text) + + def __str__(self) -> str: + return self.get_text(colorize_console()) + + +class AnsiText: + def __init__(self, *args: T.List[T.Union[str, AnsiDecorator]]): + self.args = args + + def __len__(self) -> int: + return sum((len(x) for x in self.args)) + + def __str__(self) -> str: + return ''.join((str(x) for x in self.args)) + + def bold(text: str, quoted: bool = False) -> AnsiDecorator: return AnsiDecorator(text, "\033[1m", quoted=quoted) diff --git a/mesonbuild/scripts/depscan.py b/mesonbuild/scripts/depscan.py index df7df48..c85f8e7 100644 --- a/mesonbuild/scripts/depscan.py +++ b/mesonbuild/scripts/depscan.py @@ -46,7 +46,7 @@ class DependencyScanner: self.sources_with_exports = [] # type: T.List[str] def scan_file(self, fname: str) -> None: - suffix = os.path.splitext(fname)[1][1:] + suffix = os.path.splitext(fname)[1][1:].lower() if suffix in lang_suffixes['fortran']: self.scan_fortran_file(fname) elif suffix in lang_suffixes['cpp']: @@ -131,7 +131,7 @@ class DependencyScanner: return objname def module_name_for(self, src: str) -> str: - suffix= os.path.splitext(src)[1][1:] + suffix = os.path.splitext(src)[1][1:].lower() if suffix in lang_suffixes['fortran']: exported = self.exports[src] # Module foo:bar goes to a file name foo@bar.smod diff --git a/run_mypy.py b/run_mypy.py index daf7431..e6900c7 100755 --- a/run_mypy.py +++ b/run_mypy.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 -import sys -import subprocess -import argparse from pathlib import Path +import argparse +import os +import subprocess +import sys import typing as T modules = [ @@ -24,7 +25,8 @@ modules = [ 'mesonbuild/interpreterbase.py', 'mesonbuild/linkers.py', 'mesonbuild/mcompile.py', - 'mesonbuild/mesonlib.py', + 'mesonbuild/mesonlib/platform.py', + 'mesonbuild/mesonlib/universal.py', 'mesonbuild/minit.py', 'mesonbuild/minstall.py', 'mesonbuild/mintro.py', @@ -40,6 +42,11 @@ modules = [ 'tools' ] +if os.name == 'posix': + modules.append('mesonbuild/mesonlib/posix.py') +elif os.name == 'nt': + modules.append('mesonbuild/mesonlib/win32.py') + def check_mypy() -> None: try: import mypy diff --git a/run_project_tests.py b/run_project_tests.py index 772aa2e..33641d7 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -43,6 +43,7 @@ from mesonbuild import mlog from mesonbuild import mtest from mesonbuild.build import ConfigurationData from mesonbuild.mesonlib import MachineChoice, Popen_safe, TemporaryDirectoryWinProof +from mesonbuild.mlog import bold, green, red, yellow from mesonbuild.coredata import backendlist, version as meson_version from run_tests import get_fake_options, run_configure, get_meson_script @@ -345,22 +346,6 @@ def log_text_file(logfile, testdir, stdo, stde): raise StopException() -def bold(text): - return mlog.bold(text).get_text(mlog.colorize_console()) - - -def green(text): - return mlog.green(text).get_text(mlog.colorize_console()) - - -def red(text): - return mlog.red(text).get_text(mlog.colorize_console()) - - -def yellow(text): - return mlog.yellow(text).get_text(mlog.colorize_console()) - - def _run_ci_include(args: T.List[str]) -> str: if not args: return 'At least one parameter required' diff --git a/run_tests.py b/run_tests.py index da894c0..24d6a51 100755 --- a/run_tests.py +++ b/run_tests.py @@ -290,7 +290,7 @@ def run_configure(commandlist, env=None): return run_configure_inprocess(commandlist, env=env) def print_system_info(): - print(mlog.bold('System information.').get_text(mlog.colorize_console())) + print(mlog.bold('System information.')) print('Architecture:', platform.architecture()) print('Machine:', platform.machine()) print('Platform:', platform.system()) @@ -364,7 +364,7 @@ def main(): print(flush=True) returncode = 0 else: - print(mlog.bold('Running unittests.').get_text(mlog.colorize_console())) + print(mlog.bold('Running unittests.')) print(flush=True) cmd = mesonlib.python_command + ['run_unittests.py', '-v'] if options.failfast: @@ -377,7 +377,7 @@ def main(): else: cross_test_args = mesonlib.python_command + ['run_cross_test.py'] for cf in options.cross: - print(mlog.bold('Running {} cross tests.'.format(cf)).get_text(mlog.colorize_console())) + print(mlog.bold('Running {} cross tests.'.format(cf))) print(flush=True) cmd = cross_test_args + ['cross/' + cf] if options.failfast: diff --git a/run_unittests.py b/run_unittests.py index da6d329..b244a07 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -4707,6 +4707,12 @@ class AllPlatformTests(BasePlatformTests): no : NO coma list : a, b, c + Stuff + missing prog : NO + existing prog : ''' + sys.executable + ''' + missing dep : NO + internal dep : YES + Plugins long coma list : alpha, alphacolor, apetag, audiofx, audioparsers, auparse, autodetect, avi @@ -33,6 +33,7 @@ packages = ['mesonbuild', 'mesonbuild.compilers', 'mesonbuild.compilers.mixins', 'mesonbuild.dependencies', + 'mesonbuild.mesonlib', 'mesonbuild.modules', 'mesonbuild.scripts', 'mesonbuild.templates', diff --git a/test cases/fortran/21 install static/main.f90 b/test cases/fortran/21 install static/main.f90 new file mode 100644 index 0000000..c83a6a0 --- /dev/null +++ b/test cases/fortran/21 install static/main.f90 @@ -0,0 +1,4 @@ +use main_lib +implicit none +call main_hello() +end program
\ No newline at end of file diff --git a/test cases/fortran/21 install static/main_lib.f90 b/test cases/fortran/21 install static/main_lib.f90 new file mode 100644 index 0000000..5f3cb45 --- /dev/null +++ b/test cases/fortran/21 install static/main_lib.f90 @@ -0,0 +1,16 @@ +module main_lib + + use static_hello + implicit none + + private + public :: main_hello + + contains + + subroutine main_hello + call static_say_hello() + print *, "Main hello routine finished." + end subroutine main_hello + +end module main_lib diff --git a/test cases/fortran/21 install static/meson.build b/test cases/fortran/21 install static/meson.build new file mode 100644 index 0000000..a91613f --- /dev/null +++ b/test cases/fortran/21 install static/meson.build @@ -0,0 +1,20 @@ +# Based on 'fortran/5 static', but: +# - Uses a subproject dependency +# - Is an install:true static library to trigger certain codepath (promotion to link_whole) +# - Does fortran code 'generation' with configure_file +# - Uses .F90 ext (capital F typically denotes a dependence on preprocessor treatment, which however is not used) +project('try-static-subproject-dependency', 'fortran', default_options: ['default_library=static']) + +static_dep = dependency('static_hello', fallback: ['static_hello', 'static_hello_dep']) + +mainsrc = 'main_lib.f90' +mainsrc = configure_file( + command: [find_program('cp'), '@INPUT@', '@OUTPUT@'], + input: mainsrc, + output: 'main_lib_output.F90' +) +main_lib = library('mainstatic', mainsrc, dependencies: static_dep, install: true) +main_dep = declare_dependency(link_with: main_lib) + +main_exe = executable('main_exe', 'main.f90', dependencies: main_dep) +test('static_subproject_test', main_exe) diff --git a/test cases/fortran/21 install static/subprojects/static_hello/meson.build b/test cases/fortran/21 install static/subprojects/static_hello/meson.build new file mode 100644 index 0000000..7edca39 --- /dev/null +++ b/test cases/fortran/21 install static/subprojects/static_hello/meson.build @@ -0,0 +1,12 @@ +project('static-hello', 'fortran') + +# staticlibsource = 'static_hello.f90' +staticlibsource = configure_file( + command: [find_program('cp'), '@INPUT@', '@OUTPUT@'], + input: 'static_hello.f90', + output: 'static_hello_output.F90' +) + +static_hello_lib = static_library('static_hello', staticlibsource, install: false) + +static_hello_dep = declare_dependency(link_with: static_hello_lib) diff --git a/test cases/fortran/21 install static/subprojects/static_hello/static_hello.f90 b/test cases/fortran/21 install static/subprojects/static_hello/static_hello.f90 new file mode 100644 index 0000000..5407560 --- /dev/null +++ b/test cases/fortran/21 install static/subprojects/static_hello/static_hello.f90 @@ -0,0 +1,17 @@ +module static_hello +implicit none + +private +public :: static_say_hello + +interface static_say_hello + module procedure say_hello +end interface static_say_hello + +contains + +subroutine say_hello + print *, "Static library called." +end subroutine say_hello + +end module static_hello diff --git a/test cases/fortran/21 install static/test.json b/test cases/fortran/21 install static/test.json new file mode 100644 index 0000000..b31e91d --- /dev/null +++ b/test cases/fortran/21 install static/test.json @@ -0,0 +1,5 @@ +{ + "installed": [ + {"file": "usr/lib/libmainstatic.a", "type": "file"} + ] +}
\ No newline at end of file diff --git a/test cases/unit/73 summary/meson.build b/test cases/unit/73 summary/meson.build index 1bc05ca..50383b4 100644 --- a/test cases/unit/73 summary/meson.build +++ b/test cases/unit/73 summary/meson.build @@ -9,6 +9,11 @@ summary({'Some boolean': false, 'A list': ['string', 1, true], 'empty list': [], }, section: 'Configuration') +summary({'missing prog': find_program('xyzzy', required: false), + 'existing prog': import('python').find_installation(), + 'missing dep': dependency('', required: false), + 'internal dep': declare_dependency(), + }, section: 'Stuff') summary('A number', 1, section: 'Configuration') summary('yes', true, bool_yn : true, section: 'Configuration') summary('no', false, bool_yn : true, section: 'Configuration') |