diff options
30 files changed, 891 insertions, 491 deletions
@@ -2,7 +2,7 @@ /mesonbuild/modules/pkgconfig.py @xclaesse /mesonbuild/modules/cmake.py @mensinda /mesonbuild/modules/unstable_external_project.py @xclaesse -/mesonbuild/ast/* @mensinda -/mesonbuild/cmake/* @mensinda -/mesonbuild/compilers/* @dcbaker +/mesonbuild/ast/ @mensinda +/mesonbuild/cmake/ @mensinda +/mesonbuild/compilers/ @dcbaker /mesonbuild/linkers.py @dcbaker diff --git a/data/shell-completions/zsh/_meson b/data/shell-completions/zsh/_meson index e7fe968..f8946de 100644 --- a/data/shell-completions/zsh/_meson +++ b/data/shell-completions/zsh/_meson @@ -1,4 +1,4 @@ -#compdef meson mesonconf=meson-configure mesontest=meson-test mesonintrospect=meson-introspect +#compdef meson # vim:ts=2 sw=2 @@ -32,13 +32,55 @@ local -i ret local __meson_backends="(ninja xcode ${(j. .)${:-vs{,2010,2015,2017}}})" local __meson_build_types="(plain debug debugoptimized minsize release)" local __meson_wrap_modes="(WrapMode.{default,nofallback,nodownload,forcefallback})" +local __meson_dist_formats=("xztar" "gztar" "zip") +local __meson_cd='-C[change into this directory before running]:target dir:_directories' +local -a __meson_common=( + '--prefix=[installation prefix]: :_directories' + '--bindir=[executable directory]: :_directories' + '--datadir=[data file directory]: :_directories' + '--includedir=[header file directory]: :_directories' + '--infodir=[info page directory]: :_directories' + '--libdir=[library directory]: :_directories' + '--libexecdir=[library executable directory]: :_directories' + '--localedir=[locale data directory]: :_directories' + '--localstatedir=[local state data directory]: :_directories' + '--mandir=[manual page directory]: :_directories' + '--sbindir=[system executable directory]: :_directories' + '--sharedstatedir=[arch-independent data directory]: :_directories' + '--sysconfdir=[system configuration directory]: :_directories' + '--auto-features=[default value for auto features]:auto features types:(auto disabled enabled)' + '--backend=[backend to use]:Meson backend:'"$__meson_backends" + '--buildtype=[build type to use]:Meson build type:'"$__meson_build_types" + '--debug[turn on building with debug]' + '--default-library=[default library type]:default library type:(shared static both)' + '--errorlogs[prints the logs from failing tests]' + '--install-umask=[default umask for permissions of all installed files]' + '--layout=[build directory layout]:build directory layout:(flat mirror)' + '--optimization=[optimization level for compiled targets]:optimization:(0 g 1 2 3 s)' + '--stdsplit=[split stdout and stderr in test logs]' + '--strip[strip targets on install]' + '--unity=[unity builds on/off]:whether to do unity builds:(on off subprojects)' + '--warnlevel=[compiler warning level]:compiler warning level:warning level:(1 2 3)' + '--werror[treat warnings as errors]' + '--wrap-mode=[special wrap mode]:wrap mode:'"$__meson_wrap_modes" + '--force-fallback-for=[force fallback for listed subprojects]' + '--pkg-config-path=[extra paths for HOST pkg-config to search]:paths:_dir_list -s ,' + '--build.pkg-config-path=[extra paths for BUILD pkg-config to search]:paths:_dir_list -s ,' + '--cmake-prefix-path=[extra prefixes for HOST cmake to search]:paths:_dir_list -s ,' + '--build.cmake-prefix-path=[extra prefix for BUILD cmake to search]:paths:_dir_list -s ,' +) local -a meson_commands=( -'setup:set up a build directory' 'configure:configure a project' -'test:run tests' +'dist:generate release archive' +'init:create a new project' +'install:install one more more targets' 'introspect:query project properties' +'setup:set up a build directory' +'test:run tests' 'wrap:manage source dependencies' +'subprojects:manage subprojects' +'compile:Build the project' ) (( $+functions[__meson_is_build_dir] )) || __meson_is_build_dir() { @@ -68,6 +110,21 @@ local -a meson_commands=( fi } +(( $+functions[__meson_wrap_names] )) || __meson_wrap_names() { + local rwraps + rwraps="$(_call_program meson meson wrap list)" + local -a wraps=(${(@f)rwraps}) + _describe -t wraps "Meson wraps" wraps +} + +(( $+functions[__meson_installed_wraps] )) || __meson_installed_wraps() { + local rwraps + if rwraps="$(ls subprojects/ | grep '\.wrap$' | cut -d . -f 1)"; then + local -a wraps=(${(@f)rwraps}) + _describe -t wraps "Meson wraps" wraps + fi +} + (( $+functions[_meson_commands] )) || _meson_commands() { _describe -t commands "Meson subcommands" meson_commands } @@ -89,49 +146,30 @@ local -a meson_commands=( _arguments \ '*-D-[set the value of a build option]:build option:__meson_build_options' \ - '--prefix=[installation prefix]: :_directories' \ - '--libdir=[library directory]: :_directories' \ - '--libexecdir=[library executable directory]: :_directories' \ - '--bindir=[executable directory]: :_directories' \ - '--sbindir=[system executable directory]: :_directories' \ - '--includedir=[header file directory]: :_directories' \ - '--datadir=[data file directory]: :_directories' \ - '--mandir=[manual page directory]: :_directories' \ - '--infodir=[info page directory]: :_directories' \ - '--localedir=[locale data directory]: :_directories' \ - '--sysconfdir=[system configuration directory]: :_directories' \ - '--localstatedir=[local state data directory]: :_directories' \ - '--sharedstatedir=[arch-independent data directory]: :_directories' \ - '--backend=[backend to use]:Meson backend:'"$__meson_backends" \ - '--buildtype=[build type to use]:Meson build type:'"$__meson_build_types" \ - '--strip[strip targets on install]' \ - '--unity=[unity builds on/off]:whether to do unity builds:(on off subprojects)' \ - '--werror[treat warnings as errors]' \ - '--layout=[build directory layout]:build directory layout:(flat mirror)' \ - '--default-library=[default library type]:default library type:(shared static)' \ - '--warnlevel=[compiler warning level]:compiler warning level:warning level:(1 2 3)' \ - '--stdsplit=[split stdout and stderr in test logs]' \ - '--errorlogs=[prints the logs from failing tests]' \ '--cross-file=[cross-compilation environment description]:cross file:_files' \ '--native-file=[build machine compilation environment description]:native file:_files' \ - '--wrap-mode=[special wrap mode]:wrap mode:'"$__meson_wrap_modes" \ + '--clearcache[clear cached state]' \ + '--fatal-meson-warnings=[exit when any meson warnings are encountered]' \ + '(-v --version)'{'-v','--version'}'[print the meson version and exit]' \ + '--reconfigure=[re-run build configuration]' \ + '--wipe=[delete saved state and restart using saved command line options]' \ ":$firstd directory:_directories" \ "::$secondd directory:_directories" \ - # + "${(@)__meson_common}" } (( $+functions[_meson-configure] )) || _meson-configure() { local curcontext="$curcontext" # TODO: implement 'mesonconf @file' local -a specs=( - '--clearcache[clear cached state]' '*-D-[set the value of a build option]:build option:__meson_build_options' '::build directory:_directories' ) _arguments \ '(: -)'{'--help','-h'}'[show a help message and quit]' \ - "${(@)specs}" + "${(@)specs}" \ + "${(@)__meson_common}" } (( $+functions[_meson-test] )) || _meson-test() { @@ -139,22 +177,23 @@ local -a meson_commands=( # TODO: complete test suites local -a specs=( - '(--quiet -q)'{'--quiet','-q'}'[produce less output to the terminal]' - '(--verbose -v)'{'--verbose','-v'}'[do not redirect stdout and stderr]' - '(--timeout-multiplier -t)'{'--timeout-multiplier','-t'}'[a multiplier for test timeouts]:Python floating-point number: ' - '-C[directory to cd into]: :_directories' '--repeat[number of times to run the tests]:number of times to repeat: ' '--no-rebuild[do not rebuild before running tests]' '--gdb[run tests under gdb]' + '--gdb-path=[program to run for gdb (can be wrapper or compaitble program)]:program:_path_commands' '--list[list available tests]' '(--wrapper --wrap)'{'--wrapper=','--wrap='}'[wrapper to run tests with]:wrapper program:_path_commands' - '(--no-suite)--suite[only run tests from this suite]:test suite: ' + "$__meson_cd" '(--suite)--no-suite[do not run tests from this suite]:test suite: ' + '(--no-suite)--suite[only run tests from this suite]:test suite: ' '--no-stdsplit[do not split stderr and stdout in logs]' '--print-errorlogs[print logs for failing tests]' '--benchmark[run benchmarks instead of tests]' '--logbase[base name for log file]:filename: ' '--num-processes[how many threads to use]:number of processes: ' + '(--verbose -v)'{'--verbose','-v'}'[do not redirect stdout and stderr]' + '(--quiet -q)'{'--quiet','-q'}'[produce less output to the terminal]' + '(--timeout-multiplier -t)'{'--timeout-multiplier','-t'}'[a multiplier for test timeouts]:Python floating-point number: ' '--setup[which test setup to use]:test setup: ' '--test-args[arguments to pass to the tests]: : ' '*:Meson tests:__meson_test_names' @@ -165,17 +204,32 @@ local -a meson_commands=( "${(@)specs}" } +(( $+functions[_meson-install] )) || _meson-install() { + local curcontext="$curcontext" + local -a specs=( + "$__meson_cd" + '--no-rebuild[Do not rebuild before installing]' + '--only-changed[Do not overwrite files that are older than the copied file]' + '--quiet[Do not print every fiel that was installed]' + ) +_arguments \ + '(: -)'{'--help','-h'}'[show a help message and quit]' \ + "${(@)specs}" +} + (( $+functions[_meson-introspect] )) || _meson-introspect() { local curcontext="$curcontext" local -a specs=( - '--targets[list top level targets]' - '--installed[list all installed files and directories]' - '--buildsystem-files[list files that belong to the build system]' - '--buildoptions[list all build options]' - '--tests[list all unit tests]' + '--ast[dump the ASK of the meson file]' '--benchmarks[list all benchmarks]' + '--buildoptions[list all build options]' + '--buildsystem-files[list files that belong to the build system]' '--dependencies[list external dependencies]' + '--installed[list all installed files and directories]' '--projectinfo[show project information]' + '--targets[list top level targets]' + '--tests[list all unit tests]' + '--backend=[backend to use]:Meson backend:'"$__meson_backends" '::build directory:_directories' ) _arguments \ @@ -183,8 +237,167 @@ _arguments \ "${(@)specs}" } +(( $+functions[_meson-init] )) || _meson-init() { + local curcontext="$curcontext" + local -a specs=( + "$__meson_cd" + '(-n --name)'{'-n','--name'}'=[the name of the project (defaults to directory name)]' + '(-e --executable)'{'-e','--executable'}'=[the name of the executable target to create (defaults to project name)]' + '(-d --deps)'{'-d','--deps'}'=[comma seperated list of dependencies]' + '(-l --language)'{'-l','--language'}'=[comma seperated list of languages (autodetected based on sources if unset)]:languages:_values , (c cpp cs cuda d fortran java objc objcpp rust)' + '(-b --build)'{'-b','--build'}'[build the project immediately after generation]' + '--builddir=[directory for building]:directory:_directories' + '(-f --force)'{'-f','--force'}'[overwrite any existing files and directories]' + '(-t --type)'{'-t','--type'}'=[project type, defaults to executable]:type:(executable library)' + '(-v --version)'{'-v','--version'}'[print the meson version and exit]' + ) +_arguments \ + '(: -)'{'--help','-h'}'[show a help message and quit]' \ + "${(@)specs}" +} + (( $+functions[_meson-wrap] )) || _meson-wrap() { - # TODO + local -a commands=( + 'list:list all available wraps' + 'search:search the db by name' + 'install:install the specified project' + 'update:Update a project to its newest available version' + 'info:Show info about a wrap' + 'status:Show the status of your subprojects' + ) + + if (( CURRENT == 2 )); then + _describe -t commands "Meson wrap subcommands" commands + else + local curcontext="$curcontext" + cmd="${${commands[(r)$words[2]:*]%%:*}}" + if (( $#cmd )); then + if [[ $cmd == status ]]; then + _message "no options" + elif [[ $cmd == "list" ]]; then + _arguments '*:meson wraps' + elif [[ $cmd == "search" ]]; then + _arguments '*:meson wraps' + elif [[ $cmd == "install" ]]; then + _arguments '*:meson wraps:__meson_wrap_names' + elif [[ $cmd == "update" ]]; then + _arguments '*:meson wraps:__meson_installed_wraps' + elif [[ $cmd == "info" ]]; then + _arguments '*:meson wraps:__meson_wrap_name' + elif [[ $cmd == "status" ]]; then + _arguments '*:' + elif [[ $cmd == "promote" ]]; then + # TODO: how do you figure out what wraps are provided by subprojects if + # they haven't been fetched yet? + _arguments '*:' + fi + else + _message "unknown meson wrap command: $words[2]" + fi + fi + +} + +(( $+functions[_meson-dist] )) || _meson-dist() { + local curcontext="$curcontext" + local -a specs=( + '--formats=[comma seperated list of archive types to create]:archive formats:_values -s , format '"$__meson_dist_formats" + '--include-subprojects[Include source code of subprojects that have been used for the build]' + '--no-tests[Do not build and test generated packages]' + "$__meson_cd" + ) +_arguments \ + '(: -)'{'--help','-h'}'[show a help message and quit]' \ + "${(@)specs}" +} + +(( $+functions[_meson-subprojects-update] )) || _meson-subprojects-update() { + local curcontext="$curcontext" + local -a specs=( + "--rebase[rebase your branch on top of wrap's revision (git only)]" + '--sourcedir=[path to source directory]:_directories' + '*:subprojects:__meson_installed_wraps' + ) +_arguments \ + '(: -)'{'--help','-h'}'[show a help message and quit]' \ + "${(@)specs}" +} + +(( $+functions[_meson-subprojects-checkout] )) || _meson-subprojects-checkout() { + local curcontext="$curcontext" + local -a specs=( + '-b[create a new branch]' + '--sourcedir=[path to source directory]:_directories' + # FIXME: this doesn't work exactly right, but I can't figure it out + ':branch name' + '*:subprojects:__meson_installed_wraps' + ) +_arguments \ + '(: -)'{'--help','-h'}'[show a help message and quit]' \ + "${(@)specs}" +} + +(( $+functions[_meson-subprojects-download] )) || _meson-subprojects-download() { + local curcontext="$curcontext" + local -a specs=( + '--sourcedir=[path to source directory]:_directories' + ) +_arguments \ + '(: -)'{'--help','-h'}'[show a help message and quit]' \ + "${(@)specs}" +} + +(( $+functions[_meson-subprojects-foreach] )) || _meson-subprojects-foreach() { + local curcontext="$curcontext" + local -a specs=( + '--sourcedir=[path to source directory]:_directories' + '*:command:_command_names -e' + ) +_arguments \ + '(: -)'{'--help','-h'}'[show a help message and quit]' \ + "${(@)specs}" +} + +(( $+functions[_meson-subprojects] )) || _meson-subprojects() { + local -a commands=( + 'update:update all subprojects from wrap files' + 'checkout:checkout a branch (git only)' + 'download:ensure subprojects are fetched, even if not in use. Already downloaded subprojects are not modified.' + 'foreach:execute a command in each subproject directory' + ) + + if (( CURRENT == 2 )); then + _describe -t commands "Meson subproject subcommands" commands + else + local curcontext="$curcontext" + cmd="${${commands[(r)$words[2]:*]%%:*}}" + if (( $#cmd )); then + if [[ $cmd == status ]]; then + _message "no options" + else + _meson-subprojects-$cmd + fi + else + _message "unknown meson subproject command: $words[2]" + fi + fi + +} + +(( $+functions[_meson-compile] )) || _meson-compile() { + local curcontext="$curcontext" + local -a specs=( + "$__meson_cd" + '--clean[Clean the build directory]' + '(-j --jobs)'{'-j','--jobs'}'=[the number fo work jobs to run (if supported)]:_guard "[0-9]#" "number of jobs"' + '(-l --load-averate)'{'-l','--load-average'}'=[the system load average to try to maintain (if supported)]:_guard "[0-9]#" "load average"' + '(-v --verbose)'{'-v','--verbose'}'[Show more output]' + '--ninja-args=[Arguments to pass to ninja (only when using ninja)]' + '--vs-args=[Arguments to pass to vs (only when using msbuild)]' + ) +_arguments \ + '(: -)'{'--help','-h'}'[show a help message and quit]' \ + "${(@)specs}" } if [[ $service != meson ]]; then diff --git a/docs/markdown/Custom-build-targets.md b/docs/markdown/Custom-build-targets.md index f0b50d8..76bf939 100644 --- a/docs/markdown/Custom-build-targets.md +++ b/docs/markdown/Custom-build-targets.md @@ -29,14 +29,15 @@ it does for source generation. See [Generating Sources](Generating-sources.md) for more information on this topic. -## Details on compiler invocations +## Details on command invocation Meson only permits you to specify one command to run. This is by design as writing shell pipelines into build definition files leads to -code that is very hard to maintain. If your compilation requires +code that is very hard to maintain. If your command requires multiple steps you need to write a wrapper script that does all the -necessary work. When doing this you need to be mindful of the -following issues: +necessary work. + +When doing this you need to be mindful of the following issues: * do not assume that the command is invoked in any specific directory * a target called `target` file `outfile` defined in subdir `subdir` diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index c289397..d315b53 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -397,6 +397,10 @@ the following special string substitutions: The returned object also has methods that are documented in the [object methods section](#custom-target-object) below. +**Note:** Assuming that `command:` is executed by a POSIX `sh` shell is not +portable, notably to Windows. Instead, consider using a `native: true` +[executable()](#executable), or a python script. + ### declare_dependency() ``` meson @@ -1804,17 +1808,23 @@ the following methods. or `xcode`. - `build_root()`: returns a string with the absolute path to the build - root directory. Note: this function will return the build root of - the parent project if called from a subproject, which is usually - not what you want. Try using `current_build_dir()`. + root directory. *(deprecated since 0.56.0)*: this function will return the + build root of the parent project if called from a subproject, which is usually + not what you want. Try using `current_build_dir()` or `project_build_root()`. - `source_root()`: returns a string with the absolute path to the source root directory. Note: you should use the `files()` function to refer to files in the root source directory instead of - constructing paths manually with `meson.source_root()`. This - function will return the source root of the parent project if called - from a subproject, which is usually not what you want. Try using - `current_source_dir()`. + constructing paths manually with `meson.source_root()`. + *(deprecated since 0.56.0)*: This function will return the source root of the + parent project if called from a subproject, which is usually not what you want. + Try using `current_source_dir()` or `project_source_root()`. + +- `project_build_root()` *(since 0.56.0)*: returns a string with the absolute path + to the build root directory of the current (sub)project. + +- `project_source_root()` *(since 0.56.0)*: returns a string with the absolute path + to the source root directory of the current (sub)project. - `current_build_dir()`: returns a string with the absolute path to the current build directory. diff --git a/docs/markdown/Tutorial.md b/docs/markdown/Tutorial.md index f108c0c..bf337ba 100644 --- a/docs/markdown/Tutorial.md +++ b/docs/markdown/Tutorial.md @@ -75,12 +75,23 @@ When Meson is run it prints the following output. Now we are ready to build our code. + +```console +$ cd builddir +$ ninja ``` + +If your Meson version is newer than 0.55.0, you can use the new +backend-agnostic build command: + +```console $ cd builddir $ meson compile ``` -Once that is done we can run the resulting binary. +For the rest of this document we are going to use the latter form. + +Once the executable is built we can run it. ```console $ ./demo @@ -135,7 +146,7 @@ need to recreate our build directory, run any sort of magical commands or the like. Instead we just type the exact same command as if we were rebuilding our code without any build system changes. -``` +```console $ meson compile ``` diff --git a/docs/markdown/snippets/deprecate_source_build_root.md b/docs/markdown/snippets/deprecate_source_build_root.md new file mode 100644 index 0000000..1cebef4 --- /dev/null +++ b/docs/markdown/snippets/deprecate_source_build_root.md @@ -0,0 +1,10 @@ +## `meson.build_root()` and `meson.source_root()` are deprecated + +Those function are common source of issue when used in a subproject because they +point to the parent project root which is rarely what is expected and is a +violation of subproject isolation. + +`meson.current_source_dir()` and `meson.current_build_dir()` should be used instead +and have been available in all Meson versions. New functions `meson.project_source_root()` +and `meson.project_build_root()` have been added in Meson 0.56.0 to get the root +of the current (sub)project. diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 85d5eb6..65a8932 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -114,6 +114,7 @@ class ExecutableSerialisation: self.workdir = workdir self.extra_paths = extra_paths self.capture = capture + self.pickled = False class TestSerialisation: def __init__(self, name: str, project: str, suite: str, fname: T.List[str], @@ -395,13 +396,30 @@ class Backend: exe_cmd = ['mono'] + exe_cmd exe_wrapper = None - force_serialize = force_serialize or extra_paths or workdir or \ - exe_wrapper or any('\n' in c for c in cmd_args) + reasons = [] + if extra_paths: + reasons.append('to set PATH') + + if exe_wrapper: + reasons.append('to use exe_wrapper') + + if workdir: + reasons.append('to set workdir') + + if any('\n' in c for c in cmd_args): + reasons.append('because command contains newlines') + + force_serialize = force_serialize or bool(reasons) + + if capture: + reasons.append('to capture output') + if not force_serialize: if not capture: - return None - return (self.environment.get_build_command() + - ['--internal', 'exe', '--capture', capture, '--'] + exe_cmd + cmd_args) + return None, '' + return ((self.environment.get_build_command() + + ['--internal', 'exe', '--capture', capture, '--'] + exe_cmd + cmd_args), + ', '.join(reasons)) workdir = workdir or self.environment.get_build_dir() env = {} @@ -425,7 +443,8 @@ class Backend: exe_wrapper, workdir, extra_paths, capture) pickle.dump(es, f) - return self.environment.get_build_command() + ['--internal', 'exe', '--unpickle', exe_data] + return (self.environment.get_build_command() + ['--internal', 'exe', '--unpickle', exe_data], + ', '.join(reasons)) def serialize_tests(self): test_data = os.path.join(self.environment.get_scratch_dir(), 'meson_test_setup.dat') @@ -685,6 +704,10 @@ class Backend: commands += compiler.get_buildtype_args(self.get_option_for_target('buildtype', target)) commands += compiler.get_optimization_args(self.get_option_for_target('optimization', target)) commands += compiler.get_debug_args(self.get_option_for_target('debug', target)) + # MSVC debug builds have /ZI argument by default and /Zi is added with debug flag + # /ZI needs to be removed in that case to avoid cl's warning to that effect (D9025 : overriding '/ZI' with '/Zi') + if ('/ZI' in commands) and ('/Zi' in commands): + commands.remove('/Zi') # Add compile args added using add_project_arguments() commands += self.build.get_project_args(compiler, target.subproject, target.for_machine) # Add compile args added using add_global_arguments() diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index f89b917..a086d37 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -870,7 +870,7 @@ int dummy; (srcs, ofilenames, cmd) = self.eval_custom_target_command(target) deps = self.unwrap_dep_list(target) deps += self.get_custom_target_depend_files(target) - desc = 'Generating {0} with a {1} command' + desc = 'Generating {0} with a custom command{1}' if target.build_always_stale: deps.append('PHONY') if target.depfile is None: @@ -884,14 +884,14 @@ int dummy; for output in d.get_outputs(): elem.add_dep(os.path.join(self.get_target_dir(d), output)) - meson_exe_cmd = self.as_meson_exe_cmdline(target.name, target.command[0], cmd[1:], - extra_bdeps=target.get_transitive_build_target_deps(), - capture=ofilenames[0] if target.capture else None) + meson_exe_cmd, reason = self.as_meson_exe_cmdline(target.name, target.command[0], cmd[1:], + extra_bdeps=target.get_transitive_build_target_deps(), + capture=ofilenames[0] if target.capture else None) if meson_exe_cmd: cmd = meson_exe_cmd - cmd_type = 'meson_exe.py custom' + cmd_type = ' (wrapped by meson {})'.format(reason) else: - cmd_type = 'custom' + cmd_type = '' if target.depfile is not None: depfile = target.get_dep_outname(elem.infilenames) rel_dfile = os.path.join(self.get_target_dir(target), depfile) @@ -2012,9 +2012,9 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) outfilelist = outfilelist[len(generator.outputs):] args = self.replace_paths(target, args, override_subdir=subdir) cmdlist = exe_arr + self.replace_extra_args(args, genlist) - meson_exe_cmd = self.as_meson_exe_cmdline('generator ' + cmdlist[0], - cmdlist[0], cmdlist[1:], - capture=outfiles[0] if generator.capture else None) + meson_exe_cmd, reason = self.as_meson_exe_cmdline('generator ' + cmdlist[0], + cmdlist[0], cmdlist[1:], + capture=outfiles[0] if generator.capture else None) if meson_exe_cmd: cmdlist = meson_exe_cmd abs_pdir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target)) @@ -2026,11 +2026,16 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) elem.add_item('DEPFILE', depfile) if len(extra_dependencies) > 0: elem.add_dep(extra_dependencies) + if len(generator.outputs) == 1: - elem.add_item('DESC', 'Generating {!r}.'.format(sole_output)) + what = '{!r}'.format(sole_output) else: # since there are multiple outputs, we log the source that caused the rebuild - elem.add_item('DESC', 'Generating source from {!r}.'.format(sole_output)) + what = 'from {!r}.'.format(sole_output) + if reason: + reason = ' (wrapped by meson {})'.format(reason) + elem.add_item('DESC', 'Generating {}{}.'.format(what, reason)) + if isinstance(exe, build.BuildTarget): elem.add_dep(self.get_target_filename(exe)) elem.add_item('COMMAND', cmdlist) diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index 3b0f842..021a1da 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -150,7 +150,7 @@ class Vs2010Backend(backends.Backend): # Always use a wrapper because MSBuild eats random characters when # there are many arguments. tdir_abs = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target)) - cmd = self.as_meson_exe_cmdline( + cmd, _ = self.as_meson_exe_cmdline( 'generator ' + cmd[0], cmd[0], cmd[1:], @@ -182,6 +182,8 @@ class Vs2010Backend(backends.Backend): else: raise MesonException('Unsupported Visual Studio platform: ' + target_machine) self.buildtype = self.environment.coredata.get_builtin_option('buildtype') + self.optimization = self.environment.coredata.get_builtin_option('optimization') + self.debug = self.environment.coredata.get_builtin_option('debug') sln_filename = os.path.join(self.environment.get_build_dir(), self.build.project_name + '.sln') projlist = self.generate_projects() self.gen_testproj('RUN_TESTS', os.path.join(self.environment.get_build_dir(), 'RUN_TESTS.vcxproj')) @@ -565,12 +567,12 @@ class Vs2010Backend(backends.Backend): # there are many arguments. tdir_abs = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target)) extra_bdeps = target.get_transitive_build_target_deps() - wrapper_cmd = self.as_meson_exe_cmdline(target.name, target.command[0], cmd[1:], - # All targets run from the target dir - workdir=tdir_abs, - extra_bdeps=extra_bdeps, - capture=ofilenames[0] if target.capture else None, - force_serialize=True) + wrapper_cmd, _ = self.as_meson_exe_cmdline(target.name, target.command[0], cmd[1:], + # All targets run from the target dir + workdir=tdir_abs, + extra_bdeps=extra_bdeps, + capture=ofilenames[0] if target.capture else None, + force_serialize=True) if target.build_always_stale: # Use a nonexistent file to always consider the target out-of-date. ofilenames += [self.nonexistent_file(os.path.join(self.environment.get_scratch_dir(), @@ -774,7 +776,9 @@ class Vs2010Backend(backends.Backend): if self.is_unity(target): sources = self.generate_unity_files(target, sources) compiler = self._get_cl_compiler(target) - buildtype_args = compiler.get_buildtype_args(self.buildtype) + build_args = compiler.get_buildtype_args(self.buildtype) + build_args += compiler.get_optimization_args(self.optimization) + build_args += compiler.get_debug_args(self.debug) buildtype_link_args = compiler.get_buildtype_linker_args(self.buildtype) vscrt_type = self.environment.coredata.base_options['b_vscrt'] project_name = target.name @@ -844,18 +848,20 @@ class Vs2010Backend(backends.Backend): ET.SubElement(type_config, 'UseDebugLibraries').text = 'false' ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDLL' # Debug format - if '/ZI' in buildtype_args: + if '/ZI' in build_args: ET.SubElement(clconf, 'DebugInformationFormat').text = 'EditAndContinue' - elif '/Zi' in buildtype_args: + elif '/Zi' in build_args: ET.SubElement(clconf, 'DebugInformationFormat').text = 'ProgramDatabase' - elif '/Z7' in buildtype_args: + elif '/Z7' in build_args: ET.SubElement(clconf, 'DebugInformationFormat').text = 'OldStyle' + else: + ET.SubElement(clconf, 'DebugInformationFormat').text = 'None' # Runtime checks - if '/RTC1' in buildtype_args: + if '/RTC1' in build_args: ET.SubElement(clconf, 'BasicRuntimeChecks').text = 'EnableFastChecks' - elif '/RTCu' in buildtype_args: + elif '/RTCu' in build_args: ET.SubElement(clconf, 'BasicRuntimeChecks').text = 'UninitializedLocalUsageCheck' - elif '/RTCs' in buildtype_args: + elif '/RTCs' in build_args: ET.SubElement(clconf, 'BasicRuntimeChecks').text = 'StackFrameRuntimeCheck' # Exception handling has to be set in the xml in addition to the "AdditionalOptions" because otherwise # cl will give warning D9025: overriding '/Ehs' with cpp_eh value @@ -1022,6 +1028,8 @@ class Vs2010Backend(backends.Backend): target_args.append(arg) languages += gen_langs + if '/Gw' in build_args: + target_args.append('/Gw') if len(target_args) > 0: target_args.append('%(AdditionalOptions)') ET.SubElement(clconf, "AdditionalOptions").text = ' '.join(target_args) @@ -1035,7 +1043,7 @@ class Vs2010Backend(backends.Backend): if self.get_option_for_target('werror', target): ET.SubElement(clconf, 'TreatWarningAsError').text = 'true' # Optimization flags - o_flags = split_o_flags_args(buildtype_args) + o_flags = split_o_flags_args(build_args) if '/Ox' in o_flags: ET.SubElement(clconf, 'Optimization').text = 'Full' elif '/O2' in o_flags: @@ -1087,8 +1095,10 @@ class Vs2010Backend(backends.Backend): # AdditionalOptions? extra_link_args += compiler.get_buildtype_linker_args(self.buildtype) # Generate Debug info - if self.buildtype.startswith('debug'): + if self.debug: self.generate_debug_information(link) + else: + ET.SubElement(link, 'GenerateDebugInformation').text = 'false' if not isinstance(target, build.StaticLibrary): if isinstance(target, build.SharedModule): options = self.environment.coredata.base_options @@ -1186,7 +1196,7 @@ class Vs2010Backend(backends.Backend): if target.vs_module_defs: relpath = os.path.join(down, target.vs_module_defs.rel_to_builddir(self.build_to_src)) ET.SubElement(link, 'ModuleDefinitionFile').text = relpath - if '/ZI' in buildtype_args or '/Zi' in buildtype_args: + if self.debug: pdb = ET.SubElement(link, 'ProgramDataBaseFileName') pdb.text = '$(OutDir}%s.pdb' % target_name targetmachine = ET.SubElement(link, 'TargetMachine') diff --git a/mesonbuild/cmake/client.py b/mesonbuild/cmake/client.py index b88a673..2089be4 100644 --- a/mesonbuild/cmake/client.py +++ b/mesonbuild/cmake/client.py @@ -17,7 +17,6 @@ from .common import CMakeException, CMakeConfiguration, CMakeBuildFile from .executor import CMakeExecutor -from ..environment import Environment from ..mesonlib import MachineChoice from .. import mlog from contextlib import contextmanager @@ -26,6 +25,9 @@ import typing as T import json import os +if T.TYPE_CHECKING: + from ..environment import Environment + CMAKE_SERVER_BEGIN_STR = '[== "CMake Server" ==[' CMAKE_SERVER_END_STR = ']== "CMake Server" ==]' @@ -36,7 +38,7 @@ CMAKE_MESSAGE_TYPES = { 'progress': ['cookie'], 'reply': ['cookie', 'inReplyTo'], 'signal': ['cookie', 'name'], -} +} # type: T.Dict[str, T.List[str]] CMAKE_REPLY_TYPES = { 'handshake': [], @@ -44,16 +46,16 @@ CMAKE_REPLY_TYPES = { 'compute': [], 'cmakeInputs': ['buildFiles', 'cmakeRootDirectory', 'sourceDirectory'], 'codemodel': ['configurations'] -} +} # type: T.Dict[str, T.List[str]] # Base CMake server message classes class MessageBase: - def __init__(self, msg_type: str, cookie: str): + def __init__(self, msg_type: str, cookie: str) -> None: self.type = msg_type self.cookie = cookie - def to_dict(self) -> dict: + def to_dict(self) -> T.Dict[str, T.Union[str, T.List[str], T.Dict[str, int]]]: return {'type': self.type, 'cookie': self.cookie} def log(self) -> None: @@ -62,21 +64,21 @@ class MessageBase: class RequestBase(MessageBase): cookie_counter = 0 - def __init__(self, msg_type: str): + def __init__(self, msg_type: str) -> None: super().__init__(msg_type, self.gen_cookie()) @staticmethod - def gen_cookie(): + def gen_cookie() -> str: RequestBase.cookie_counter += 1 return 'meson_{}'.format(RequestBase.cookie_counter) class ReplyBase(MessageBase): - def __init__(self, cookie: str, in_reply_to: str): + def __init__(self, cookie: str, in_reply_to: str) -> None: super().__init__('reply', cookie) self.in_reply_to = in_reply_to class SignalBase(MessageBase): - def __init__(self, cookie: str, signal_name: str): + def __init__(self, cookie: str, signal_name: str) -> None: super().__init__('signal', cookie) self.signal_name = signal_name @@ -86,7 +88,7 @@ class SignalBase(MessageBase): # Special Message classes class Error(MessageBase): - def __init__(self, cookie: str, message: str): + def __init__(self, cookie: str, message: str) -> None: super().__init__('error', cookie) self.message = message @@ -94,7 +96,7 @@ class Error(MessageBase): mlog.error(mlog.bold('CMake server error:'), mlog.red(self.message)) class Message(MessageBase): - def __init__(self, cookie: str, message: str): + def __init__(self, cookie: str, message: str) -> None: super().__init__('message', cookie) self.message = message @@ -103,19 +105,21 @@ class Message(MessageBase): pass class Progress(MessageBase): - def __init__(self, cookie: str): + def __init__(self, cookie: str) -> None: super().__init__('progress', cookie) def log(self) -> None: pass class MessageHello(MessageBase): - def __init__(self, supported_protocol_versions: T.List[dict]): + def __init__(self, supported_protocol_versions: T.List[T.Dict[str, int]]) -> None: super().__init__('hello', '') self.supported_protocol_versions = supported_protocol_versions def supports(self, major: int, minor: T.Optional[int] = None) -> bool: for i in self.supported_protocol_versions: + assert 'major' in i + assert 'minor' in i if major == i['major']: if minor is None or minor == i['minor']: return True @@ -124,7 +128,7 @@ class MessageHello(MessageBase): # Request classes class RequestHandShake(RequestBase): - def __init__(self, src_dir: str, build_dir: str, generator: str, vers_major: int, vers_minor: T.Optional[int] = None): + def __init__(self, src_dir: str, build_dir: str, generator: str, vers_major: int, vers_minor: T.Optional[int] = None) -> None: super().__init__('handshake') self.src_dir = src_dir self.build_dir = build_dir @@ -132,7 +136,7 @@ class RequestHandShake(RequestBase): self.vers_major = vers_major self.vers_minor = vers_minor - def to_dict(self) -> dict: + def to_dict(self) -> T.Dict[str, T.Union[str, T.List[str], T.Dict[str, int]]]: vers = {'major': self.vers_major} if self.vers_minor is not None: vers['minor'] = self.vers_minor @@ -154,40 +158,40 @@ class RequestConfigure(RequestBase): super().__init__('configure') self.args = args - def to_dict(self) -> dict: + def to_dict(self) -> T.Dict[str, T.Union[str, T.List[str], T.Dict[str, int]]]: res = super().to_dict() if self.args: res['cacheArguments'] = self.args return res class RequestCompute(RequestBase): - def __init__(self): + def __init__(self) -> None: super().__init__('compute') class RequestCMakeInputs(RequestBase): - def __init__(self): + def __init__(self) -> None: super().__init__('cmakeInputs') class RequestCodeModel(RequestBase): - def __init__(self): + def __init__(self) -> None: super().__init__('codemodel') # Reply classes class ReplyHandShake(ReplyBase): - def __init__(self, cookie: str): + def __init__(self, cookie: str) -> None: super().__init__(cookie, 'handshake') class ReplyConfigure(ReplyBase): - def __init__(self, cookie: str): + def __init__(self, cookie: str) -> None: super().__init__(cookie, 'configure') class ReplyCompute(ReplyBase): - def __init__(self, cookie: str): + def __init__(self, cookie: str) -> None: super().__init__(cookie, 'compute') class ReplyCMakeInputs(ReplyBase): - def __init__(self, cookie: str, cmake_root: str, src_dir: str, build_files: T.List[CMakeBuildFile]): + def __init__(self, cookie: str, cmake_root: str, src_dir: str, build_files: T.List[CMakeBuildFile]) -> None: super().__init__(cookie, 'cmakeInputs') self.cmake_root = cmake_root self.src_dir = src_dir @@ -202,7 +206,7 @@ class ReplyCMakeInputs(ReplyBase): mlog.log(str(i)) class ReplyCodeModel(ReplyBase): - def __init__(self, data: dict): + def __init__(self, data: T.Dict[str, T.Any]) -> None: super().__init__(data['cookie'], 'codemodel') self.configs = [] for i in data['configurations']: @@ -218,9 +222,9 @@ class ReplyCodeModel(ReplyBase): # Main client class class CMakeClient: - def __init__(self, env: Environment): + def __init__(self, env: 'Environment') -> None: self.env = env - self.proc = None + self.proc = None # type: T.Optional[Popen] self.type_map = { 'error': lambda data: Error(data['cookie'], data['errorMessage']), 'hello': lambda data: MessageHello(data['supportedProtocolVersions']), @@ -228,7 +232,7 @@ class CMakeClient: 'progress': lambda data: Progress(data['cookie']), 'reply': self.resolve_type_reply, 'signal': lambda data: SignalBase(data['cookie'], data['name']) - } + } # type: T.Dict[str, T.Callable[[T.Dict[str, T.Any]], MessageBase]] self.reply_map = { 'handshake': lambda data: ReplyHandShake(data['cookie']), @@ -236,10 +240,10 @@ class CMakeClient: 'compute': lambda data: ReplyCompute(data['cookie']), 'cmakeInputs': self.resolve_reply_cmakeInputs, 'codemodel': lambda data: ReplyCodeModel(data), - } + } # type: T.Dict[str, T.Callable[[T.Dict[str, T.Any]], ReplyBase]] - def readMessageRaw(self) -> dict: - assert(self.proc is not None) + def readMessageRaw(self) -> T.Dict[str, T.Any]: + assert self.proc is not None rawData = [] begin = False while self.proc.poll() is None: @@ -257,7 +261,11 @@ class CMakeClient: begin = True # Begin of the message if rawData: - return json.loads('\n'.join(rawData)) + res = json.loads('\n'.join(rawData)) + assert isinstance(res, dict) + for i in res.keys(): + assert isinstance(i, str) + return res raise CMakeException('Failed to read data from the CMake server') def readMessage(self) -> MessageBase: @@ -287,7 +295,7 @@ class CMakeClient: reply.log() - def query_checked(self, request: RequestBase, message: str) -> ReplyBase: + def query_checked(self, request: RequestBase, message: str) -> MessageBase: reply = self.query(request) h = mlog.green('SUCCEEDED') if reply.type == 'reply' else mlog.red('FAILED') mlog.log(message + ':', h) @@ -305,7 +313,7 @@ class CMakeClient: request = RequestHandShake(src_dir, build_dir, generator, vers_major, vers_minor) self.query_checked(request, 'CMake server handshake') - def resolve_type_reply(self, data: dict) -> ReplyBase: + def resolve_type_reply(self, data: T.Dict[str, T.Any]) -> ReplyBase: reply_type = data['inReplyTo'] func = self.reply_map.get(reply_type, None) if not func: @@ -315,7 +323,7 @@ class CMakeClient: raise CMakeException('Key "{}" is missing from CMake server message type {}'.format(i, type)) return func(data) - def resolve_reply_cmakeInputs(self, data: dict) -> ReplyCMakeInputs: + def resolve_reply_cmakeInputs(self, data: T.Dict[str, T.Any]) -> ReplyCMakeInputs: files = [] for i in data['buildFiles']: for j in i['sources']: @@ -323,7 +331,7 @@ class CMakeClient: return ReplyCMakeInputs(data['cookie'], data['cmakeRootDirectory'], data['sourceDirectory'], files) @contextmanager - def connect(self): + def connect(self) -> T.Generator[None, None, None]: self.startup() try: yield diff --git a/mesonbuild/cmake/common.py b/mesonbuild/cmake/common.py index 4510b5d..f6bd944 100644 --- a/mesonbuild/cmake/common.py +++ b/mesonbuild/cmake/common.py @@ -23,12 +23,12 @@ class CMakeException(MesonException): pass class CMakeBuildFile: - def __init__(self, file: str, is_cmake: bool, is_temp: bool): + def __init__(self, file: str, is_cmake: bool, is_temp: bool) -> None: self.file = file self.is_cmake = is_cmake self.is_temp = is_temp - def __repr__(self): + def __repr__(self) -> str: return '<{}: {}; cmake={}; temp={}>'.format(self.__class__.__name__, self.file, self.is_cmake, self.is_temp) def _flags_to_list(raw: str) -> T.List[str]: @@ -80,29 +80,37 @@ def cmake_defines_to_args(raw: T.Any, permissive: bool = False) -> T.List[str]: return res +class CMakeInclude: + def __init__(self, path: str, isSystem: bool = False): + self.path = path + self.isSystem = isSystem + + def __repr__(self) -> str: + return '<CMakeInclude: {} -- isSystem = {}>'.format(self.path, self.isSystem) + class CMakeFileGroup: - def __init__(self, data: dict): - self.defines = data.get('defines', '') - self.flags = _flags_to_list(data.get('compileFlags', '')) - self.includes = data.get('includePath', []) - self.is_generated = data.get('isGenerated', False) - self.language = data.get('language', 'C') - self.sources = data.get('sources', []) + def __init__(self, data: T.Dict[str, T.Any]) -> None: + self.defines = data.get('defines', '') # type: str + self.flags = _flags_to_list(data.get('compileFlags', '')) # type: T.List[str] + self.is_generated = data.get('isGenerated', False) # type: bool + self.language = data.get('language', 'C') # type: str + self.sources = data.get('sources', []) # type: T.List[str] # Fix the include directories - tmp = [] - for i in self.includes: + self.includes = [] # type: T.List[CMakeInclude] + for i in data.get('includePath', []): if isinstance(i, dict) and 'path' in i: - i['isSystem'] = i.get('isSystem', False) - tmp += [i] + isSystem = i.get('isSystem', False) + assert isinstance(isSystem, bool) + assert isinstance(i['path'], str) + self.includes += [CMakeInclude(i['path'], isSystem)] elif isinstance(i, str): - tmp += [{'path': i, 'isSystem': False}] - self.includes = tmp + self.includes += [CMakeInclude(i)] def log(self) -> None: mlog.log('flags =', mlog.bold(', '.join(self.flags))) mlog.log('defines =', mlog.bold(', '.join(self.defines))) - mlog.log('includes =', mlog.bold(', '.join(self.includes))) + mlog.log('includes =', mlog.bold(', '.join([str(x) for x in self.includes]))) mlog.log('is_generated =', mlog.bold('true' if self.is_generated else 'false')) mlog.log('language =', mlog.bold(self.language)) mlog.log('sources:') @@ -111,22 +119,22 @@ class CMakeFileGroup: mlog.log(i) class CMakeTarget: - def __init__(self, data: dict): - self.artifacts = data.get('artifacts', []) - self.src_dir = data.get('sourceDirectory', '') - self.build_dir = data.get('buildDirectory', '') - self.name = data.get('name', '') - self.full_name = data.get('fullName', '') - self.install = data.get('hasInstallRule', False) - self.install_paths = list(set(data.get('installPaths', []))) - self.link_lang = data.get('linkerLanguage', '') - self.link_libraries = _flags_to_list(data.get('linkLibraries', '')) - self.link_flags = _flags_to_list(data.get('linkFlags', '')) - self.link_lang_flags = _flags_to_list(data.get('linkLanguageFlags', '')) - # self.link_path = data.get('linkPath', '') - self.type = data.get('type', 'EXECUTABLE') - # self.is_generator_provided = data.get('isGeneratorProvided', False) - self.files = [] + def __init__(self, data: T.Dict[str, T.Any]) -> None: + self.artifacts = data.get('artifacts', []) # type: T.List[str] + self.src_dir = data.get('sourceDirectory', '') # type: str + self.build_dir = data.get('buildDirectory', '') # type: str + self.name = data.get('name', '') # type: str + self.full_name = data.get('fullName', '') # type: str + self.install = data.get('hasInstallRule', False) # type: bool + self.install_paths = list(set(data.get('installPaths', []))) # type: T.List[str] + self.link_lang = data.get('linkerLanguage', '') # type: str + self.link_libraries = _flags_to_list(data.get('linkLibraries', '')) # type: T.List[str] + self.link_flags = _flags_to_list(data.get('linkFlags', '')) # type: T.List[str] + self.link_lang_flags = _flags_to_list(data.get('linkLanguageFlags', '')) # type: T.List[str] + # self.link_path = data.get('linkPath', '') # type: str + self.type = data.get('type', 'EXECUTABLE') # type: str + # self.is_generator_provided = data.get('isGeneratorProvided', False) # type: bool + self.files = [] # type: T.List[CMakeFileGroup] for i in data.get('fileGroups', []): self.files += [CMakeFileGroup(i)] @@ -152,11 +160,11 @@ class CMakeTarget: i.log() class CMakeProject: - def __init__(self, data: dict): - self.src_dir = data.get('sourceDirectory', '') - self.build_dir = data.get('buildDirectory', '') - self.name = data.get('name', '') - self.targets = [] + def __init__(self, data: T.Dict[str, T.Any]) -> None: + self.src_dir = data.get('sourceDirectory', '') # type: str + self.build_dir = data.get('buildDirectory', '') # type: str + self.name = data.get('name', '') # type: str + self.targets = [] # type: T.List[CMakeTarget] for i in data.get('targets', []): self.targets += [CMakeTarget(i)] @@ -171,9 +179,9 @@ class CMakeProject: i.log() class CMakeConfiguration: - def __init__(self, data: dict): - self.name = data.get('name', '') - self.projects = [] + def __init__(self, data: T.Dict[str, T.Any]) -> None: + self.name = data.get('name', '') # type: str + self.projects = [] # type: T.List[CMakeProject] for i in data.get('projects', []): self.projects += [CMakeProject(i)] diff --git a/mesonbuild/cmake/executor.py b/mesonbuild/cmake/executor.py index 2226c02..d3588f0 100644 --- a/mesonbuild/cmake/executor.py +++ b/mesonbuild/cmake/executor.py @@ -38,7 +38,8 @@ if T.TYPE_CHECKING: from ..dependencies.base import ExternalProgram from ..compilers import Compiler -TYPE_result = T.Tuple[int, T.Optional[str], T.Optional[str]] +TYPE_result = T.Tuple[int, T.Optional[str], T.Optional[str]] +TYPE_cache_key = T.Tuple[str, T.Tuple[str, ...], str, T.FrozenSet[T.Tuple[str, str]]] _MESON_TO_CMAKE_MAPPING = { 'arm': 'ARMCC', @@ -76,8 +77,8 @@ def meson_compiler_to_cmake_id(cobj: 'Compiler') -> str: class CMakeExecutor: # The class's copy of the CMake path. Avoids having to search for it # multiple times in the same Meson invocation. - class_cmakebin = PerMachine(None, None) - class_cmakevers = PerMachine(None, None) + class_cmakebin = PerMachine(None, None) # type: PerMachine[T.Optional[ExternalProgram]] + class_cmakevers = PerMachine(None, None) # type: PerMachine[T.Optional[str]] class_cmake_cache = {} # type: T.Dict[T.Any, TYPE_result] def __init__(self, environment: Environment, version: str, for_machine: MachineChoice, silent: bool = False): @@ -89,8 +90,8 @@ class CMakeExecutor: self.print_cmout = False self.prefix_paths = [] # type: T.List[str] self.extra_cmake_args = [] # type: T.List[str] - if self.cmakebin is False: - self.cmakebin = None + + if self.cmakebin is None: return if not version_compare(self.cmakevers, self.min_version): @@ -102,17 +103,18 @@ class CMakeExecutor: return self.prefix_paths = self.environment.coredata.builtins_per_machine[self.for_machine]['cmake_prefix_path'].value - env_pref_path = get_env_var( + env_pref_path_raw = get_env_var( self.for_machine, self.environment.is_cross_build(), 'CMAKE_PREFIX_PATH') - if env_pref_path is not None: + if env_pref_path_raw is not None: + env_pref_path = [] # type: T.List[str] if mesonlib.is_windows(): # Cannot split on ':' on Windows because its in the drive letter - env_pref_path = env_pref_path.split(os.pathsep) + env_pref_path = env_pref_path_raw.split(os.pathsep) else: # https://github.com/mesonbuild/meson/issues/7294 - env_pref_path = re.split(r':|;', env_pref_path) + env_pref_path = re.split(r':|;', env_pref_path_raw) env_pref_path = [x for x in env_pref_path if x] # Filter out empty strings if not self.prefix_paths: self.prefix_paths = [] @@ -121,13 +123,14 @@ class CMakeExecutor: if self.prefix_paths: self.extra_cmake_args += ['-DCMAKE_PREFIX_PATH={}'.format(';'.join(self.prefix_paths))] - def find_cmake_binary(self, environment: Environment, silent: bool = False) -> T.Tuple['ExternalProgram', str]: - from ..dependencies.base import find_external_program + def find_cmake_binary(self, environment: Environment, silent: bool = False) -> T.Tuple[T.Optional['ExternalProgram'], T.Optional[str]]: + from ..dependencies.base import find_external_program, NonExistingExternalProgram # Only search for CMake the first time and store the result in the class # definition - if CMakeExecutor.class_cmakebin[self.for_machine] is False: + if isinstance(CMakeExecutor.class_cmakebin[self.for_machine], NonExistingExternalProgram): mlog.debug('CMake binary for %s is cached as not found' % self.for_machine) + return None, None elif CMakeExecutor.class_cmakebin[self.for_machine] is not None: mlog.debug('CMake binary for %s is cached.' % self.for_machine) else: @@ -142,7 +145,7 @@ class CMakeExecutor: continue if not silent: mlog.log('Found CMake:', mlog.bold(potential_cmakebin.get_path()), - '(%s)' % version_if_ok) + '({})'.format(version_if_ok)) CMakeExecutor.class_cmakebin[self.for_machine] = potential_cmakebin CMakeExecutor.class_cmakevers[self.for_machine] = version_if_ok break @@ -151,8 +154,9 @@ class CMakeExecutor: mlog.log('Found CMake:', mlog.red('NO')) # Set to False instead of None to signify that we've already # searched for it and not found it - CMakeExecutor.class_cmakebin[self.for_machine] = False + CMakeExecutor.class_cmakebin[self.for_machine] = NonExistingExternalProgram() CMakeExecutor.class_cmakevers[self.for_machine] = None + return None, None return CMakeExecutor.class_cmakebin[self.for_machine], CMakeExecutor.class_cmakevers[self.for_machine] @@ -185,19 +189,19 @@ class CMakeExecutor: if always_capture_stderr is not None: self.always_capture_stderr = always_capture_stderr - def _cache_key(self, args: T.List[str], build_dir: str, env): - fenv = frozenset(env.items()) if env is not None else None + def _cache_key(self, args: T.List[str], build_dir: str, env: T.Optional[T.Dict[str, str]]) -> TYPE_cache_key: + fenv = frozenset(env.items()) if env is not None else frozenset() targs = tuple(args) - return (self.cmakebin, targs, build_dir, fenv) + return (self.cmakebin.get_path(), targs, build_dir, fenv) - def _call_cmout_stderr(self, args: T.List[str], build_dir: str, env) -> TYPE_result: + def _call_cmout_stderr(self, args: T.List[str], build_dir: str, env: T.Optional[T.Dict[str, str]]) -> TYPE_result: cmd = self.cmakebin.get_command() + args proc = S.Popen(cmd, stdout=S.PIPE, stderr=S.PIPE, cwd=build_dir, env=env) # stdout and stderr MUST be read at the same time to avoid pipe # blocking issues. The easiest way to do this is with a separate # thread for one of the pipes. - def print_stdout(): + def print_stdout() -> None: while True: line = proc.stdout.readline() if not line: @@ -214,10 +218,10 @@ class CMakeExecutor: tline_start_reg = re.compile(r'^\s*(.*\.(cmake|txt))\(([0-9]+)\):\s*(\w+)\(.*$') inside_multiline_trace = False while True: - line = proc.stderr.readline() - if not line: + line_raw = proc.stderr.readline() + if not line_raw: break - line = line.decode(errors='ignore') + line = line_raw.decode(errors='ignore') if tline_start_reg.match(line): raw_trace += line inside_multiline_trace = not line.endswith(' )\n') @@ -233,7 +237,7 @@ class CMakeExecutor: return proc.returncode, None, raw_trace - def _call_cmout(self, args: T.List[str], build_dir: str, env) -> TYPE_result: + def _call_cmout(self, args: T.List[str], build_dir: str, env: T.Optional[T.Dict[str, str]]) -> TYPE_result: cmd = self.cmakebin.get_command() + args proc = S.Popen(cmd, stdout=S.PIPE, stderr=S.STDOUT, cwd=build_dir, env=env) while True: @@ -245,7 +249,7 @@ class CMakeExecutor: proc.wait() return proc.returncode, None, None - def _call_quiet(self, args: T.List[str], build_dir: str, env) -> TYPE_result: + def _call_quiet(self, args: T.List[str], build_dir: str, env: T.Optional[T.Dict[str, str]]) -> TYPE_result: os.makedirs(build_dir, exist_ok=True) cmd = self.cmakebin.get_command() + args ret = S.run(cmd, env=env, cwd=build_dir, close_fds=False, @@ -257,7 +261,7 @@ class CMakeExecutor: mlog.debug("Called `{}` in {} -> {}".format(call, build_dir, rc)) return rc, out, err - def _call_impl(self, args: T.List[str], build_dir: str, env) -> TYPE_result: + def _call_impl(self, args: T.List[str], build_dir: str, env: T.Optional[T.Dict[str, str]]) -> TYPE_result: if not self.print_cmout: return self._call_quiet(args, build_dir, env) else: @@ -266,9 +270,9 @@ class CMakeExecutor: else: return self._call_cmout(args, build_dir, env) - def call(self, args: T.List[str], build_dir: str, env=None, disable_cache: bool = False) -> TYPE_result: + def call(self, args: T.List[str], build_dir: str, env: T.Optional[T.Dict[str, str]] = None, disable_cache: bool = False) -> TYPE_result: if env is None: - env = os.environ + env = os.environ.copy() args = args + self.extra_cmake_args if disable_cache: @@ -281,7 +285,7 @@ class CMakeExecutor: cache[key] = self._call_impl(args, build_dir, env) return cache[key] - def call_with_fake_build(self, args: T.List[str], build_dir: str, env=None) -> TYPE_result: + def call_with_fake_build(self, args: T.List[str], build_dir: str, env: T.Optional[T.Dict[str, str]] = None) -> TYPE_result: # First check the cache cache = CMakeExecutor.class_cmake_cache key = self._cache_key(args, build_dir, env) @@ -369,7 +373,7 @@ class CMakeExecutor: set(CMAKE_C_IGNORE_EXTENSIONS h;H;o;O;obj;OBJ;def;DEF;rc;RC) set(CMAKE_SIZEOF_VOID_P "{}") '''.format(c_comp, c_launcher, is_gnu, c_id, c_version, - ctypes.sizeof(ctypes.c_voidp)))) + ctypes.sizeof(ctypes.c_void_p)))) if cxx_comp and not cxx_comp_file.is_file(): is_gnu = '1' if cxx_id == 'GNU' else '' @@ -388,7 +392,7 @@ class CMakeExecutor: set(CMAKE_CXX_SOURCE_FILE_EXTENSIONS C;M;c++;cc;cpp;cxx;mm;CPP) set(CMAKE_SIZEOF_VOID_P "{}") '''.format(cxx_comp, cxx_launcher, is_gnu, cxx_id, cxx_version, - ctypes.sizeof(ctypes.c_voidp)))) + ctypes.sizeof(ctypes.c_void_p)))) if fortran_comp and not fortran_comp_file.is_file(): fortran_comp_file.write_text(textwrap.dedent('''\ @@ -403,7 +407,7 @@ class CMakeExecutor: set(CMAKE_Fortran_IGNORE_EXTENSIONS h;H;o;O;obj;OBJ;def;DEF;rc;RC) set(CMAKE_Fortran_SOURCE_FILE_EXTENSIONS f;F;fpp;FPP;f77;F77;f90;F90;for;For;FOR;f95;F95) set(CMAKE_SIZEOF_VOID_P "{}") - '''.format(fortran_comp, fortran_launcher, ctypes.sizeof(ctypes.c_voidp)))) + '''.format(fortran_comp, fortran_launcher, ctypes.sizeof(ctypes.c_void_p)))) return self.call(args, build_dir, env) diff --git a/mesonbuild/cmake/fileapi.py b/mesonbuild/cmake/fileapi.py index f219f16..0405145 100644 --- a/mesonbuild/cmake/fileapi.py +++ b/mesonbuild/cmake/fileapi.py @@ -27,8 +27,8 @@ class CMakeFileAPI: self.api_base_dir = os.path.join(self.build_dir, '.cmake', 'api', 'v1') self.request_dir = os.path.join(self.api_base_dir, 'query', 'client-meson') self.reply_dir = os.path.join(self.api_base_dir, 'reply') - self.cmake_sources = [] - self.cmake_configurations = [] + self.cmake_sources = [] # type: T.List[CMakeBuildFile] + self.cmake_configurations = [] # type: T.List[CMakeConfiguration] self.kind_resolver_map = { 'codemodel': self._parse_codemodel, 'cmakeFiles': self._parse_cmakeFiles, @@ -87,7 +87,7 @@ class CMakeFileAPI: self.kind_resolver_map[i['kind']](i) - def _parse_codemodel(self, data: dict) -> None: + def _parse_codemodel(self, data: T.Dict[str, T.Any]) -> None: assert('configurations' in data) assert('paths' in data) @@ -100,7 +100,7 @@ class CMakeFileAPI: # resolved and the resulting data structure is identical # to the CMake serve output. - def helper_parse_dir(dir_entry: dict) -> T.Tuple[str, str]: + def helper_parse_dir(dir_entry: T.Dict[str, T.Any]) -> T.Tuple[str, str]: src_dir = dir_entry.get('source', '.') bld_dir = dir_entry.get('build', '.') src_dir = src_dir if os.path.isabs(src_dir) else os.path.join(source_dir, src_dir) @@ -110,7 +110,7 @@ class CMakeFileAPI: return src_dir, bld_dir - def parse_sources(comp_group: dict, tgt: dict) -> T.Tuple[T.List[str], T.List[str], T.List[int]]: + def parse_sources(comp_group: T.Dict[str, T.Any], tgt: T.Dict[str, T.Any]) -> T.Tuple[T.List[str], T.List[str], T.List[int]]: gen = [] src = [] idx = [] @@ -127,7 +127,7 @@ class CMakeFileAPI: return src, gen, idx - def parse_target(tgt: dict) -> dict: + def parse_target(tgt: T.Dict[str, T.Any]) -> T.Dict[str, T.Any]: src_dir, bld_dir = helper_parse_dir(cnf.get('paths', {})) # Parse install paths (if present) @@ -230,7 +230,7 @@ class CMakeFileAPI: }] return tgt_data - def parse_project(pro: dict) -> dict: + def parse_project(pro: T.Dict[str, T.Any]) -> T.Dict[str, T.Any]: # Only look at the first directory specified in directoryIndexes # TODO Figure out what the other indexes are there for p_src_dir = source_dir @@ -268,7 +268,7 @@ class CMakeFileAPI: self.cmake_configurations += [CMakeConfiguration(cnf_data)] - def _parse_cmakeFiles(self, data: dict) -> None: + def _parse_cmakeFiles(self, data: T.Dict[str, T.Any]) -> None: assert('inputs' in data) assert('paths' in data) @@ -309,10 +309,14 @@ class CMakeFileAPI: return data - def _reply_file_content(self, filename: str) -> dict: + def _reply_file_content(self, filename: str) -> T.Dict[str, T.Any]: real_path = os.path.join(self.reply_dir, filename) if not os.path.exists(real_path): raise CMakeException('File "{}" does not exist'.format(real_path)) with open(real_path, 'r') as fp: - return json.load(fp) + data = json.load(fp) + assert isinstance(data, dict) + for i in data.keys(): + assert isinstance(i, str) + return data diff --git a/mesonbuild/cmake/generator.py b/mesonbuild/cmake/generator.py index a30d2de..02d56cc 100644 --- a/mesonbuild/cmake/generator.py +++ b/mesonbuild/cmake/generator.py @@ -13,6 +13,7 @@ # limitations under the License. from .. import mesonlib +import typing as T def parse_generator_expressions(raw: str) -> str: '''Parse CMake generator expressions @@ -73,7 +74,7 @@ def parse_generator_expressions(raw: str) -> str: 'ANGLE-R': lambda x: '>', 'COMMA': lambda x: ',', 'SEMICOLON': lambda x: ';', - } + } # type: T.Dict[str, T.Callable[[str], str]] # Recursively evaluate generator expressions def eval_generator_expressions() -> str: diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index 2fdb328..1a7fe7a 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -15,13 +15,12 @@ # This class contains the basic functionality needed to run any interpreter # or an interpreter-based tool. -from .common import CMakeException, CMakeTarget, TargetOptions -from .client import CMakeClient, RequestCMakeInputs, RequestConfigure, RequestCompute, RequestCodeModel +from .common import CMakeException, CMakeTarget, TargetOptions, CMakeConfiguration +from .client import CMakeClient, RequestCMakeInputs, RequestConfigure, RequestCompute, RequestCodeModel, ReplyCMakeInputs, ReplyCodeModel from .fileapi import CMakeFileAPI from .executor import CMakeExecutor from .traceparser import CMakeTraceParser, CMakeGeneratorTarget from .. import mlog, mesonlib -from ..environment import Environment from ..mesonlib import MachineChoice, OrderedSet, version_compare from ..mesondata import mesondata from ..compilers.compilers import lang_suffixes, header_suffixes, obj_suffixes, lib_suffixes, is_header @@ -51,6 +50,11 @@ from ..mparser import ( if T.TYPE_CHECKING: from ..build import Build from ..backend.backends import Backend + from ..environment import Environment + +TYPE_mixed = T.Union[str, int, bool, BaseNode] +TYPE_mixed_list = T.Union[TYPE_mixed, T.Sequence[TYPE_mixed]] +TYPE_mixed_kwargs = T.Dict[str, TYPE_mixed_list] # Disable all warnings automaticall enabled with --trace and friends # See https://cmake.org/cmake/help/latest/variable/CMAKE_POLICY_WARNING_CMPNNNN.html @@ -138,7 +142,7 @@ class OutputTargetMap: rm_so_version = re.compile(r'(\.[0-9]+)+$') def __init__(self, build_dir: str): - self.tgt_map = {} + self.tgt_map = {} # type: T.Dict[str, T.Union['ConverterTarget', 'ConverterCustomTarget']] self.build_dir = build_dir def add(self, tgt: T.Union['ConverterTarget', 'ConverterCustomTarget']) -> None: @@ -185,8 +189,10 @@ class OutputTargetMap: keys += [self._rel_artifact_key(i), os.path.basename(i), self._base_artifact_key(i)] return self._return_first_valid_key(keys) - def generated(self, name: str) -> T.Optional[T.Union['ConverterTarget', 'ConverterCustomTarget']]: - return self._return_first_valid_key([self._rel_generated_file_key(name), self._base_generated_file_key(name)]) + def generated(self, name: str) -> T.Optional['ConverterCustomTarget']: + res = self._return_first_valid_key([self._rel_generated_file_key(name), self._base_generated_file_key(name)]) + assert res is None or isinstance(res, ConverterCustomTarget) + return res # Utility functions to generate local keys def _rel_path(self, fname: str) -> T.Optional[str]: @@ -213,42 +219,44 @@ class OutputTargetMap: return '__art_{}__'.format(os.path.basename(fname)) class ConverterTarget: - def __init__(self, target: CMakeTarget, env: Environment): - self.env = env - self.artifacts = target.artifacts - self.src_dir = target.src_dir - self.build_dir = target.build_dir - self.name = target.name - self.cmake_name = target.name - self.full_name = target.full_name - self.type = target.type - self.install = target.install - self.install_dir = '' + def __init__(self, target: CMakeTarget, env: 'Environment') -> None: + self.env = env + self.artifacts = target.artifacts + self.src_dir = target.src_dir + self.build_dir = target.build_dir + self.name = target.name + self.cmake_name = target.name + self.full_name = target.full_name + self.type = target.type + self.install = target.install + self.install_dir = '' self.link_libraries = target.link_libraries - self.link_flags = target.link_flags + target.link_lang_flags - self.depends_raw = [] - self.depends = [] + self.link_flags = target.link_flags + target.link_lang_flags + self.depends_raw = [] # type: T.List[str] + self.depends = [] # type: T.List[T.Union[ConverterTarget, ConverterCustomTarget]] if target.install_paths: self.install_dir = target.install_paths[0] - self.languages = [] - self.sources = [] - self.generated = [] - self.includes = [] - self.sys_includes = [] - self.link_with = [] - self.object_libs = [] - self.compile_opts = {} - self.public_compile_opts = [] - self.pie = False + self.languages = [] # type: T.List[str] + self.sources = [] # type: T.List[str] + self.generated = [] # type: T.List[str] + self.generated_ctgt = [] # type: T.List[CustomTargetReference] + self.includes = [] # type: T.List[str] + self.sys_includes = [] # type: T.List[str] + self.link_with = [] # type: T.List[T.Union[ConverterTarget, ConverterCustomTarget]] + self.object_libs = [] # type: T.List[ConverterTarget] + self.compile_opts = {} # type: T.Dict[str, T.List[str]] + self.public_compile_opts = [] # type: T.List[str] + self.pie = False # Project default override options (c_std, cpp_std, etc.) - self.override_options = [] + self.override_options = [] # type: T.List[str] # Convert the target name to a valid meson target name self.name = _sanitize_cmake_name(self.name) + self.generated_raw = [] # type: T.List[str] for i in target.files: # Determine the meson language lang_cmake_to_meson = {val.lower(): key for key, val in language_map.items()} @@ -264,12 +272,12 @@ class ConverterTarget: self.compile_opts[lang] += [x for x in args if x not in self.compile_opts[lang]] # Handle include directories - self.includes += [x['path'] for x in i.includes if x not in self.includes and not x['isSystem']] - self.sys_includes += [x['path'] for x in i.includes if x not in self.sys_includes and x['isSystem']] + self.includes += [x.path for x in i.includes if x.path not in self.includes and not x.isSystem] + self.sys_includes += [x.path for x in i.includes if x.path not in self.sys_includes and x.isSystem] # Add sources to the right array if i.is_generated: - self.generated += i.sources + self.generated_raw += i.sources else: self.sources += i.sources @@ -306,7 +314,7 @@ class ConverterTarget: # Sometimes projects pass generated source files as compiler # flags. Add these as generated sources to ensure that the # corresponding custom target is run.2 - self.generated += [j] + self.generated_raw += [j] temp += [j] elif j in blacklist_compiler_flags: pass @@ -339,7 +347,7 @@ class ConverterTarget: cfg = '' otherDeps = [] libraries = [] - mlog.debug(tgt) + mlog.debug(str(tgt)) if 'INTERFACE_INCLUDE_DIRECTORIES' in tgt.properties: self.includes += [x for x in tgt.properties['INTERFACE_INCLUDE_DIRECTORIES'] if x] @@ -424,8 +432,8 @@ class ConverterTarget: for i in self.languages: supported += list(lang_suffixes[i]) supported = ['.{}'.format(x) for x in supported] - self.sources = [x for x in self.sources if any([x.endswith(y) for y in supported])] - self.generated = [x for x in self.generated if any([x.endswith(y) for y in supported])] + self.sources = [x for x in self.sources if any([x.endswith(y) for y in supported])] + self.generated_raw = [x for x in self.generated_raw if any([x.endswith(y) for y in supported])] # Make paths relative def rel_path(x: str, is_header: bool, is_generated: bool) -> T.Optional[str]: @@ -434,7 +442,7 @@ class ConverterTarget: x = os.path.normpath(x) if not os.path.exists(x) and not any([x.endswith(y) for y in obj_suffixes]) and not is_generated: if ( - any([os.path.commonpath([x, os.path.normpath(os.path.join(root_src_dir, y))]) == x for y in self.generated]) + any([os.path.commonpath([x, os.path.normpath(os.path.join(root_src_dir, y))]) == x for y in self.generated_raw]) and os.path.commonpath([x, self.env.get_build_dir()]) == self.env.get_build_dir() ): os.makedirs(x) @@ -465,29 +473,27 @@ class ConverterTarget: return os.path.relpath(x, root_src_dir) return x - def custom_target(x: str): - ctgt = output_target_map.generated(x) - if ctgt: - assert(isinstance(ctgt, ConverterCustomTarget)) - ref = ctgt.get_ref(x) - assert(isinstance(ref, CustomTargetReference) and ref.valid()) - return ref - return x - build_dir_rel = os.path.relpath(self.build_dir, os.path.join(self.env.get_build_dir(), subdir)) - self.generated = [rel_path(x, False, True) for x in self.generated] + self.generated_raw = [rel_path(x, False, True) for x in self.generated_raw] self.includes = list(OrderedSet([rel_path(x, True, False) for x in OrderedSet(self.includes)] + [build_dir_rel])) self.sys_includes = list(OrderedSet([rel_path(x, True, False) for x in OrderedSet(self.sys_includes)])) self.sources = [rel_path(x, False, False) for x in self.sources] # Resolve custom targets - self.generated = [custom_target(x) for x in self.generated] + for gen_file in self.generated_raw: + ctgt = output_target_map.generated(gen_file) + if ctgt: + assert isinstance(ctgt, ConverterCustomTarget) + ref = ctgt.get_ref(gen_file) + assert isinstance(ref, CustomTargetReference) and ref.valid() + self.generated_ctgt += [ref] + elif gen_file is not None: + self.generated += [gen_file] # Remove delete entries - self.includes = [x for x in self.includes if x is not None] + self.includes = [x for x in self.includes if x is not None] self.sys_includes = [x for x in self.sys_includes if x is not None] - self.sources = [x for x in self.sources if x is not None] - self.generated = [x for x in self.generated if x is not None] + self.sources = [x for x in self.sources if x is not None] # Make sure '.' is always in the include directories if '.' not in self.includes: @@ -511,20 +517,20 @@ class ConverterTarget: # Handle explicit CMake add_dependency() calls for i in self.depends_raw: - tgt = output_target_map.target(i) - if tgt: - self.depends.append(tgt) + dep_tgt = output_target_map.target(i) + if dep_tgt: + self.depends.append(dep_tgt) - def process_object_libs(self, obj_target_list: T.List['ConverterTarget'], linker_workaround: bool): + def process_object_libs(self, obj_target_list: T.List['ConverterTarget'], linker_workaround: bool) -> None: # Try to detect the object library(s) from the generated input sources - temp = [x for x in self.generated if isinstance(x, str)] + temp = [x for x in self.generated] temp = [os.path.basename(x) for x in temp] temp = [x for x in temp if any([x.endswith('.' + y) for y in obj_suffixes])] temp = [os.path.splitext(x)[0] for x in temp] exts = self._all_source_suffixes() # Temp now stores the source filenames of the object files for i in obj_target_list: - source_files = [x for x in i.sources + i.generated if isinstance(x, str)] + source_files = [x for x in i.sources + i.generated] source_files = [os.path.basename(x) for x in source_files] for j in temp: # On some platforms (specifically looking at you Windows with vs20xy backend) CMake does @@ -546,15 +552,17 @@ class ConverterTarget: break # Filter out object files from the sources - self.generated = [x for x in self.generated if not isinstance(x, str) or not any([x.endswith('.' + y) for y in obj_suffixes])] + self.generated = [x for x in self.generated if not any([x.endswith('.' + y) for y in obj_suffixes])] def _append_objlib_sources(self, tgt: 'ConverterTarget') -> None: - self.includes += tgt.includes - self.sources += tgt.sources - self.generated += tgt.generated - self.sources = list(OrderedSet(self.sources)) - self.generated = list(OrderedSet(self.generated)) - self.includes = list(OrderedSet(self.includes)) + self.includes += tgt.includes + self.sources += tgt.sources + self.generated += tgt.generated + self.generated_ctgt += tgt.generated_ctgt + self.includes = list(OrderedSet(self.includes)) + self.sources = list(OrderedSet(self.sources)) + self.generated = list(OrderedSet(self.generated)) + self.generated_ctgt = list(OrderedSet(self.generated_ctgt)) # Inherit compiler arguments since they may be required for building for lang, opts in tgt.compile_opts.items(): @@ -574,9 +582,16 @@ class ConverterTarget: lang_opts = self.env.coredata.compiler_options.build.get(lang, None) if not lang_opts or 'std' not in lang_opts: return [] - return lang_opts['std'].choices + res = lang_opts['std'].choices + + # TODO: Get rid of this once we have propper typing for options + assert isinstance(res, list) + for i in res: + assert isinstance(i, str) - def process_inter_target_dependencies(self): + return res + + def process_inter_target_dependencies(self) -> None: # Move the dependencies from all transfer_dependencies_from to the target to_process = list(self.depends) processed = [] @@ -589,7 +604,7 @@ class ConverterTarget: new_deps += [i] self.depends = list(OrderedSet(new_deps)) - def cleanup_dependencies(self): + def cleanup_dependencies(self) -> None: # Clear the dependencies from targets that where moved from if self.meson_func() in transfer_dependencies_from: self.depends = [] @@ -613,6 +628,7 @@ class ConverterTarget: mlog.log(' -- sys_includes: ', mlog.bold(str(self.sys_includes))) mlog.log(' -- sources: ', mlog.bold(str(self.sources))) mlog.log(' -- generated: ', mlog.bold(str(self.generated))) + mlog.log(' -- generated_ctgt: ', mlog.bold(str(self.generated_ctgt))) mlog.log(' -- pie: ', mlog.bold('true' if self.pie else 'false')) mlog.log(' -- override_opts: ', mlog.bold(str(self.override_options))) mlog.log(' -- depends: ', mlog.bold(str(self.depends))) @@ -621,7 +637,7 @@ class ConverterTarget: mlog.log(' -', key, '=', mlog.bold(str(val))) class CustomTargetReference: - def __init__(self, ctgt: 'ConverterCustomTarget', index: int): + def __init__(self, ctgt: 'ConverterCustomTarget', index: int) -> None: self.ctgt = ctgt # type: ConverterCustomTarget self.index = index # type: int @@ -641,24 +657,25 @@ class ConverterCustomTarget: tgt_counter = 0 # type: int out_counter = 0 # type: int - def __init__(self, target: CMakeGeneratorTarget): - assert(target.current_bin_dir is not None) - assert(target.current_src_dir is not None) + def __init__(self, target: CMakeGeneratorTarget) -> None: + assert target.current_bin_dir is not None + assert target.current_src_dir is not None self.name = target.name if not self.name: self.name = 'custom_tgt_{}'.format(ConverterCustomTarget.tgt_counter) ConverterCustomTarget.tgt_counter += 1 - self.cmake_name = str(self.name) + self.cmake_name = str(self.name) self.original_outputs = list(target.outputs) - self.outputs = [os.path.basename(x) for x in self.original_outputs] - self.conflict_map = {} - self.command = target.command - self.working_dir = target.working_dir - self.depends_raw = target.depends - self.inputs = [] - self.depends = [] - self.current_bin_dir = Path(target.current_bin_dir) - self.current_src_dir = Path(target.current_src_dir) + self.outputs = [os.path.basename(x) for x in self.original_outputs] + self.conflict_map = {} # type: T.Dict[str, str] + self.command = [] # type: T.List[T.List[T.Union[str, ConverterTarget]]] + self.working_dir = target.working_dir + self.depends_raw = target.depends + self.inputs = [] # type: T.List[T.Union[str, CustomTargetReference]] + self.depends = [] # type: T.List[T.Union[ConverterTarget, ConverterCustomTarget]] + self.current_bin_dir = Path(target.current_bin_dir) + self.current_src_dir = Path(target.current_src_dir) + self._raw_target = target # Convert the target name to a valid meson target name self.name = _sanitize_cmake_name(self.name) @@ -700,12 +717,12 @@ class ConverterCustomTarget: self.outputs = temp_outputs # Check if the command is a build target - commands = [] - for i in self.command: - assert(isinstance(i, list)) - cmd = [] + commands = [] # type: T.List[T.List[T.Union[str, ConverterTarget]]] + for curr_cmd in self._raw_target.command: + assert(isinstance(curr_cmd, list)) + cmd = [] # type: T.List[T.Union[str, ConverterTarget]] - for j in i: + for j in curr_cmd: if not j: continue target = output_target_map.executable(j) @@ -759,9 +776,11 @@ class ConverterCustomTarget: elif tgt: self.depends += [tgt] elif gen: - self.inputs += [gen.get_ref(i)] + ctgt_ref = gen.get_ref(i) + assert ctgt_ref is not None + self.inputs += [ctgt_ref] - def process_inter_target_dependencies(self): + def process_inter_target_dependencies(self) -> None: # Move the dependencies from all transfer_dependencies_from to the target to_process = list(self.depends) processed = [] @@ -799,7 +818,7 @@ class CMakeAPI(Enum): FILE = 2 class CMakeInterpreter: - def __init__(self, build: 'Build', subdir: str, src_dir: str, install_prefix: str, env: Environment, backend: 'Backend'): + def __init__(self, build: 'Build', subdir: str, src_dir: str, install_prefix: str, env: 'Environment', backend: 'Backend'): assert(hasattr(backend, 'name')) self.build = build self.subdir = subdir @@ -815,21 +834,21 @@ class CMakeInterpreter: self.fileapi = CMakeFileAPI(self.build_dir) # Raw CMake results - self.bs_files = [] - self.codemodel_configs = None - self.raw_trace = None + self.bs_files = [] # type: T.List[str] + self.codemodel_configs = None # type: T.Optional[T.List[CMakeConfiguration]] + self.raw_trace = None # type: T.Optional[str] # Analysed data - self.project_name = '' - self.languages = [] - self.targets = [] - self.custom_targets = [] # type: T.List[ConverterCustomTarget] - self.trace = CMakeTraceParser('', '') # Will be replaced in analyse + self.project_name = '' + self.languages = [] # type: T.List[str] + self.targets = [] # type: T.List[ConverterTarget] + self.custom_targets = [] # type: T.List[ConverterCustomTarget] + self.trace = CMakeTraceParser('', '') # Will be replaced in analyse self.output_target_map = OutputTargetMap(self.build_dir) # Generated meson data - self.generated_targets = {} - self.internal_name_map = {} + self.generated_targets = {} # type: T.Dict[str, T.Dict[str, T.Optional[str]]] + self.internal_name_map = {} # type: T.Dict[str, str] def configure(self, extra_cmake_options: T.List[str]) -> None: for_machine = MachineChoice.HOST # TODO make parameter @@ -933,9 +952,11 @@ class CMakeInterpreter: # Get CMake build system files bs_reply = self.client.query_checked(RequestCMakeInputs(), 'Querying build system files') + assert isinstance(bs_reply, ReplyCMakeInputs) # Now get the CMake code model cm_reply = self.client.query_checked(RequestCodeModel(), 'Querying the CMake code model') + assert isinstance(cm_reply, ReplyCodeModel) src_dir = bs_reply.src_dir self.bs_files = [x.file for x in bs_reply.build_files if not x.is_cmake and not x.is_temp] @@ -958,63 +979,64 @@ class CMakeInterpreter: # Find all targets added_target_names = [] # type: T.List[str] - for i in self.codemodel_configs: - for j in i.projects: + for i_0 in self.codemodel_configs: + for j_0 in i_0.projects: if not self.project_name: - self.project_name = j.name - for k in j.targets: + self.project_name = j_0.name + for k_0 in j_0.targets: # Avoid duplicate targets from different configurations and known # dummy CMake internal target types - if k.type not in skip_targets and k.name not in added_target_names: - added_target_names += [k.name] - self.targets += [ConverterTarget(k, self.env)] + if k_0.type not in skip_targets and k_0.name not in added_target_names: + added_target_names += [k_0.name] + self.targets += [ConverterTarget(k_0, self.env)] # Add interface targets from trace, if not already present. # This step is required because interface targets were removed from # the CMake file API output. api_target_name_list = [x.name for x in self.targets] - for i in self.trace.targets.values(): - if i.type != 'INTERFACE' or i.name in api_target_name_list or i.imported: + for i_1 in self.trace.targets.values(): + if i_1.type != 'INTERFACE' or i_1.name in api_target_name_list or i_1.imported: continue dummy = CMakeTarget({ - 'name': i.name, + 'name': i_1.name, 'type': 'INTERFACE_LIBRARY', 'sourceDirectory': self.src_dir, 'buildDirectory': self.build_dir, }) self.targets += [ConverterTarget(dummy, self.env)] - for i in self.trace.custom_targets: - self.custom_targets += [ConverterCustomTarget(i)] + for i_2 in self.trace.custom_targets: + self.custom_targets += [ConverterCustomTarget(i_2)] # generate the output_target_map - for i in [*self.targets, *self.custom_targets]: - self.output_target_map.add(i) + for i_3 in [*self.targets, *self.custom_targets]: + assert isinstance(i_3, (ConverterTarget, ConverterCustomTarget)) + self.output_target_map.add(i_3) # First pass: Basic target cleanup object_libs = [] custom_target_outputs = [] # type: T.List[str] - for i in self.custom_targets: - i.postprocess(self.output_target_map, self.src_dir, self.subdir, custom_target_outputs, self.trace) - for i in self.targets: - i.postprocess(self.output_target_map, self.src_dir, self.subdir, self.install_prefix, self.trace) - if i.type == 'OBJECT_LIBRARY': - object_libs += [i] - self.languages += [x for x in i.languages if x not in self.languages] + for ctgt in self.custom_targets: + ctgt.postprocess(self.output_target_map, self.src_dir, self.subdir, custom_target_outputs, self.trace) + for tgt in self.targets: + tgt.postprocess(self.output_target_map, self.src_dir, self.subdir, self.install_prefix, self.trace) + if tgt.type == 'OBJECT_LIBRARY': + object_libs += [tgt] + self.languages += [x for x in tgt.languages if x not in self.languages] # Second pass: Detect object library dependencies - for i in self.targets: - i.process_object_libs(object_libs, self._object_lib_workaround()) + for tgt in self.targets: + tgt.process_object_libs(object_libs, self._object_lib_workaround()) # Third pass: Reassign dependencies to avoid some loops - for i in self.targets: - i.process_inter_target_dependencies() - for i in self.custom_targets: - i.process_inter_target_dependencies() + for tgt in self.targets: + tgt.process_inter_target_dependencies() + for ctgt in self.custom_targets: + ctgt.process_inter_target_dependencies() # Fourth pass: Remove rassigned dependencies - for i in self.targets: - i.cleanup_dependencies() + for tgt in self.targets: + tgt.cleanup_dependencies() mlog.log('CMake project', mlog.bold(self.project_name), 'has', mlog.bold(str(len(self.targets) + len(self.custom_targets))), 'build targets.') @@ -1022,7 +1044,7 @@ class CMakeInterpreter: if not self.project_name: raise CMakeException('CMakeInterpreter was not analysed') - def token(tid: str = 'string', val='') -> Token: + def token(tid: str = 'string', val: TYPE_mixed = '') -> Token: return Token(tid, self.subdir, 0, 0, 0, None, val) def string(value: str) -> StringNode: @@ -1034,7 +1056,7 @@ class CMakeInterpreter: def number(value: int) -> NumberNode: return NumberNode(token(val=value)) - def nodeify(value): + def nodeify(value: TYPE_mixed_list) -> BaseNode: if isinstance(value, str): return string(value) elif isinstance(value, bool): @@ -1043,34 +1065,38 @@ class CMakeInterpreter: return number(value) elif isinstance(value, list): return array(value) - return value + elif isinstance(value, BaseNode): + return value + raise RuntimeError('invalid type of value: {} ({})'.format(type(value).__name__, str(value))) def indexed(node: BaseNode, index: int) -> IndexNode: return IndexNode(node, nodeify(index)) - def array(elements) -> ArrayNode: + def array(elements: TYPE_mixed_list) -> ArrayNode: args = ArgumentNode(token()) if not isinstance(elements, list): elements = [args] args.arguments += [nodeify(x) for x in elements if x is not None] return ArrayNode(args, 0, 0, 0, 0) - def function(name: str, args=None, kwargs=None) -> FunctionNode: + def function(name: str, args: T.Optional[TYPE_mixed_list] = None, kwargs: T.Optional[TYPE_mixed_kwargs] = None) -> FunctionNode: args = [] if args is None else args kwargs = {} if kwargs is None else kwargs args_n = ArgumentNode(token()) if not isinstance(args, list): + assert isinstance(args, (str, int, bool, BaseNode)) args = [args] args_n.arguments = [nodeify(x) for x in args if x is not None] args_n.kwargs = {id_node(k): nodeify(v) for k, v in kwargs.items() if v is not None} func_n = FunctionNode(self.subdir, 0, 0, 0, 0, name, args_n) return func_n - def method(obj: BaseNode, name: str, args=None, kwargs=None) -> MethodNode: + def method(obj: BaseNode, name: str, args: T.Optional[TYPE_mixed_list] = None, kwargs: T.Optional[TYPE_mixed_kwargs] = None) -> MethodNode: args = [] if args is None else args kwargs = {} if kwargs is None else kwargs args_n = ArgumentNode(token()) if not isinstance(args, list): + assert isinstance(args, (str, int, bool, BaseNode)) args = [args] args_n.arguments = [nodeify(x) for x in args if x is not None] args_n.kwargs = {id_node(k): nodeify(v) for k, v in kwargs.items() if v is not None} @@ -1086,9 +1112,9 @@ class CMakeInterpreter: # Add the run script for custom commands # Add the targets - processing = [] - processed = {} - name_map = {} + processing = [] # type: T.List[str] + processed = {} # type: T.Dict[str, T.Dict[str, T.Optional[str]]] + name_map = {} # type: T.Dict[str, str] def extract_tgt(tgt: T.Union[ConverterTarget, ConverterCustomTarget, CustomTargetReference]) -> IdNode: tgt_name = None @@ -1105,24 +1131,24 @@ class CMakeInterpreter: raise CMakeException('Cycle in CMake inputs/dependencies detected') processing.append(tgt.name) - def resolve_ctgt_ref(ref: CustomTargetReference) -> BaseNode: + def resolve_ctgt_ref(ref: CustomTargetReference) -> T.Union[IdNode, IndexNode]: tgt_var = extract_tgt(ref) if len(ref.ctgt.outputs) == 1: return tgt_var else: return indexed(tgt_var, ref.index) - def process_target(tgt: ConverterTarget): + def process_target(tgt: ConverterTarget) -> None: detect_cycle(tgt) # First handle inter target dependencies - link_with = [] - objec_libs = [] # type: T.List[IdNode] - sources = [] - generated = [] - generated_filenames = [] - custom_targets = [] - dependencies = [] + link_with = [] # type: T.List[IdNode] + objec_libs = [] # type: T.List[IdNode] + sources = [] # type: T.List[str] + generated = [] # type: T.List[T.Union[IdNode, IndexNode]] + generated_filenames = [] # type: T.List[str] + custom_targets = [] # type: T.List[ConverterCustomTarget] + dependencies = [] # type: T.List[IdNode] for i in tgt.link_with: assert(isinstance(i, ConverterTarget)) if i.name not in processed: @@ -1141,16 +1167,17 @@ class CMakeInterpreter: dependencies += [extract_tgt(i)] # Generate the source list and handle generated sources - for i in tgt.sources + tgt.generated: - if isinstance(i, CustomTargetReference): - if i.ctgt.name not in processed: - process_custom_target(i.ctgt) - generated += [resolve_ctgt_ref(i)] - generated_filenames += [i.filename()] - if i.ctgt not in custom_targets: - custom_targets += [i.ctgt] - else: - sources += [i] + sources += tgt.sources + sources += tgt.generated + + for ctgt_ref in tgt.generated_ctgt: + ctgt = ctgt_ref.ctgt + if ctgt.name not in processed: + process_custom_target(ctgt) + generated += [resolve_ctgt_ref(ctgt_ref)] + generated_filenames += [ctgt_ref.filename()] + if ctgt not in custom_targets: + custom_targets += [ctgt] # Add all header files from all used custom targets. This # ensures that all custom targets are built before any @@ -1158,12 +1185,12 @@ class CMakeInterpreter: # header files are present. This step is necessary because # CMake always ensures that a custom target is executed # before another target if at least one output is used. - for i in custom_targets: - for j in i.outputs: + for ctgt in custom_targets: + for j in ctgt.outputs: if not is_header(j) or j in generated_filenames: continue - generated += [resolve_ctgt_ref(i.get_ref(j))] + generated += [resolve_ctgt_ref(ctgt.get_ref(j))] generated_filenames += [j] # Determine the meson function to use for the build target @@ -1190,7 +1217,7 @@ class CMakeInterpreter: 'install': install_tgt, 'override_options': options.get_override_options(tgt.cmake_name, tgt.override_options), 'objects': [method(x, 'extract_all_objects') for x in objec_libs], - } + } # type: TYPE_mixed_kwargs # Only set if installed and only override if it is set if install_tgt and tgt.install_dir: @@ -1212,7 +1239,7 @@ class CMakeInterpreter: 'link_with': id_node(tgt_var), 'compile_args': tgt.public_compile_opts, 'include_directories': id_node(inc_var), - } + } # type: TYPE_mixed_kwargs if dependencies: generated += dependencies @@ -1230,7 +1257,7 @@ class CMakeInterpreter: tgt_var = None else: src_node = assign(src_var, function('files', sources)) - tgt_node = assign(tgt_var, function(tgt_func, [tgt_var, [id_node(src_var)] + generated], tgt_kwargs)) + tgt_node = assign(tgt_var, function(tgt_func, [tgt_var, id_node(src_var), *generated], tgt_kwargs)) node_list += [src_node, tgt_node] if tgt_func in ['static_library', 'shared_library']: dep_node = assign(dep_var, function('declare_dependency', kwargs=dep_kwargs)) @@ -1290,19 +1317,19 @@ class CMakeInterpreter: 'output': tgt.outputs, 'command': command, 'depends': [resolve_source(x) for x in tgt.depends], - } + } # type: TYPE_mixed_kwargs root_cb.lines += [assign(tgt_var, function('custom_target', [tgt.name], tgt_kwargs))] processed[tgt.name] = {'inc': None, 'src': None, 'dep': None, 'tgt': tgt_var, 'func': 'custom_target'} name_map[tgt.cmake_name] = tgt.name # Now generate the target function calls - for i in self.custom_targets: - if i.name not in processed: - process_custom_target(i) - for i in self.targets: - if i.name not in processed: - process_target(i) + for ctgt in self.custom_targets: + if ctgt.name not in processed: + process_custom_target(ctgt) + for tgt in self.targets: + if tgt.name not in processed: + process_target(tgt) self.generated_targets = processed self.internal_name_map = name_map diff --git a/mesonbuild/cmake/traceparser.py b/mesonbuild/cmake/traceparser.py index 98b56f5..5c41196 100644 --- a/mesonbuild/cmake/traceparser.py +++ b/mesonbuild/cmake/traceparser.py @@ -28,30 +28,37 @@ import json import textwrap class CMakeTraceLine: - def __init__(self, file, line, func, args): + def __init__(self, file: str, line: int, func: str, args: T.List[str]) -> None: self.file = file self.line = line self.func = func.lower() self.args = args - def __repr__(self): + def __repr__(self) -> str: s = 'CMake TRACE: {0}:{1} {2}({3})' return s.format(self.file, self.line, self.func, self.args) class CMakeTarget: - def __init__(self, name, target_type, properties=None, imported: bool = False, tline: T.Optional[CMakeTraceLine] = None): + def __init__( + self, + name: str, + target_type: str, + properties: T.Optional[T.Dict[str, T.List[str]]] = None, + imported: bool = False, + tline: T.Optional[CMakeTraceLine] = None + ): if properties is None: properties = {} - self.name = name - self.type = target_type - self.properties = properties - self.imported = imported - self.tline = tline - self.depends = [] - self.current_bin_dir = None - self.current_src_dir = None - - def __repr__(self): + self.name = name + self.type = target_type + self.properties = properties + self.imported = imported + self.tline = tline + self.depends = [] # type: T.List[str] + self.current_bin_dir = None # type: T.Optional[str] + self.current_src_dir = None # type: T.Optional[str] + + def __repr__(self) -> str: s = 'CMake TARGET:\n -- name: {}\n -- type: {}\n -- imported: {}\n -- properties: {{\n{} }}\n -- tline: {}' propSTR = '' for i in self.properties: @@ -67,14 +74,14 @@ class CMakeTarget: assert all([';' not in x for x in self.properties[key]]) class CMakeGeneratorTarget(CMakeTarget): - def __init__(self, name): + def __init__(self, name: str) -> None: super().__init__(name, 'CUSTOM', {}) self.outputs = [] # type: T.List[str] self.command = [] # type: T.List[T.List[str]] self.working_dir = None # type: T.Optional[str] class CMakeTraceParser: - def __init__(self, cmake_version: str, build_dir: str, permissive: bool = True): + def __init__(self, cmake_version: str, build_dir: str, permissive: bool = True) -> None: self.vars = {} # type: T.Dict[str, T.List[str]] self.targets = {} # type: T.Dict[str, CMakeTarget] @@ -117,7 +124,7 @@ class CMakeTraceParser: # meaning here in the trace parser. 'meson_ps_execute_delayed_calls': self._meson_ps_execute_delayed_calls, 'meson_ps_reload_vars': self._meson_ps_reload_vars, - } + } # type: T.Dict[str, T.Callable[[CMakeTraceLine], None]] def trace_args(self) -> T.List[str]: arg_map = { @@ -256,7 +263,7 @@ class CMakeTraceParser: else: self.vars[identifier] = value.split(';') - def _cmake_unset(self, tline: CMakeTraceLine): + def _cmake_unset(self, tline: CMakeTraceLine) -> None: # DOC: https://cmake.org/cmake/help/latest/command/unset.html if len(tline.args) < 1: return self._gen_exception('unset', 'requires at least one argument', tline) @@ -264,7 +271,7 @@ class CMakeTraceParser: if tline.args[0] in self.vars: del self.vars[tline.args[0]] - def _cmake_add_executable(self, tline: CMakeTraceLine): + def _cmake_add_executable(self, tline: CMakeTraceLine) -> None: # DOC: https://cmake.org/cmake/help/latest/command/add_executable.html args = list(tline.args) # Make a working copy @@ -280,7 +287,7 @@ class CMakeTraceParser: self.targets[args[0]] = CMakeTarget(args[0], 'EXECUTABLE', {}, tline=tline, imported=is_imported) - def _cmake_add_library(self, tline: CMakeTraceLine): + def _cmake_add_library(self, tline: CMakeTraceLine) -> None: # DOC: https://cmake.org/cmake/help/latest/command/add_library.html args = list(tline.args) # Make a working copy @@ -314,7 +321,7 @@ class CMakeTraceParser: else: self.targets[args[0]] = CMakeTarget(args[0], 'NORMAL', {}, tline=tline) - def _cmake_add_custom_command(self, tline: CMakeTraceLine, name=None): + def _cmake_add_custom_command(self, tline: CMakeTraceLine, name: T.Optional[str] = None) -> None: # DOC: https://cmake.org/cmake/help/latest/command/add_custom_command.html args = self._flatten_args(list(tline.args)) # Commands can be passed as ';' seperated lists @@ -379,7 +386,7 @@ class CMakeTraceParser: if name: self.targets[name] = target - def _cmake_add_custom_target(self, tline: CMakeTraceLine): + def _cmake_add_custom_target(self, tline: CMakeTraceLine) -> None: # DOC: https://cmake.org/cmake/help/latest/command/add_custom_target.html # We only the first parameter (the target name) is interesting if len(tline.args) < 1: @@ -494,7 +501,7 @@ class CMakeTraceParser: arglist = [] # type: T.List[T.Tuple[str, T.List[str]]] if self.trace_format == 'human': name = args.pop(0) - values = [] + values = [] # type: T.List[str] prop_regex = re.compile(r'^[A-Z_]+$') for a in args: if prop_regex.match(a): @@ -550,7 +557,7 @@ class CMakeTraceParser: # DOC: https://cmake.org/cmake/help/latest/command/target_link_libraries.html self._parse_common_target_options('target_link_options', 'LINK_LIBRARIES', 'INTERFACE_LINK_LIBRARIES', tline) - def _parse_common_target_options(self, func: str, private_prop: str, interface_prop: str, tline: CMakeTraceLine, ignore: T.Optional[T.List[str]] = None, paths: bool = False): + def _parse_common_target_options(self, func: str, private_prop: str, interface_prop: str, tline: CMakeTraceLine, ignore: T.Optional[T.List[str]] = None, paths: bool = False) -> None: if ignore is None: ignore = ['BEFORE'] @@ -588,11 +595,11 @@ class CMakeTraceParser: interface = [x for x in interface if x] private = [x for x in private if x] - for i in [(private_prop, private), (interface_prop, interface)]: - if not i[0] in self.targets[target].properties: - self.targets[target].properties[i[0]] = [] + for j in [(private_prop, private), (interface_prop, interface)]: + if not j[0] in self.targets[target].properties: + self.targets[target].properties[j[0]] = [] - self.targets[target].properties[i[0]] += i[1] + self.targets[target].properties[j[0]] += j[1] def _meson_ps_execute_delayed_calls(self, tline: CMakeTraceLine) -> None: for l in self.stored_commands: @@ -606,7 +613,7 @@ class CMakeTraceParser: def _meson_ps_reload_vars(self, tline: CMakeTraceLine) -> None: self.delayed_commands = self.get_cmake_var('MESON_PS_DELAYED_CALLS') - def _lex_trace_human(self, trace): + def _lex_trace_human(self, trace: str) -> T.Generator[CMakeTraceLine, None, None]: # The trace format is: '<file>(<line>): <func>(<args -- can contain \n> )\n' reg_tline = re.compile(r'\s*(.*\.(cmake|txt))\(([0-9]+)\):\s*(\w+)\(([\s\S]*?) ?\)\s*\n', re.MULTILINE) reg_other = re.compile(r'[^\n]*\n') @@ -629,17 +636,23 @@ class CMakeTraceParser: func = mo_file_line.group(4) args = mo_file_line.group(5) args = parse_generator_expressions(args) - args = args.split(' ') - args = list(map(lambda x: x.strip(), args)) + argl = args.split(' ') + argl = list(map(lambda x: x.strip(), argl)) - yield CMakeTraceLine(file, line, func, args) + yield CMakeTraceLine(file, int(line), func, argl) - def _lex_trace_json(self, trace: str): + def _lex_trace_json(self, trace: str) -> T.Generator[CMakeTraceLine, None, None]: lines = trace.splitlines(keepends=False) lines.pop(0) # The first line is the version for i in lines: data = json.loads(i) + assert isinstance(data['file'], str) + assert isinstance(data['line'], int) + assert isinstance(data['cmd'], str) + assert isinstance(data['args'], list) args = data['args'] + for j in args: + assert isinstance(j, str) args = [parse_generator_expressions(x) for x in args] yield CMakeTraceLine(data['file'], data['line'], data['cmd'], args) diff --git a/mesonbuild/compilers/mixins/visualstudio.py b/mesonbuild/compilers/mixins/visualstudio.py index d9abb95..77f8dfc 100644 --- a/mesonbuild/compilers/mixins/visualstudio.py +++ b/mesonbuild/compilers/mixins/visualstudio.py @@ -62,10 +62,10 @@ vs64_instruction_set_args = { msvc_buildtype_args = { 'plain': [], - 'debug': ["/ZI", "/Ob0", "/Od", "/RTC1"], - 'debugoptimized': ["/Zi", "/Ob1"], - 'release': ["/Ob2", "/Gw"], - 'minsize': ["/Zi", "/Gw"], + 'debug': ["/ZI", "/RTC1"], + 'debugoptimized': [], + 'release': [], + 'minsize': [], 'custom': [], } # type: T.Dict[str, T.List[str]] @@ -79,17 +79,17 @@ clangcl_buildtype_args = msvc_buildtype_args.copy() clangcl_buildtype_args['debug'] = ['/Zi', '/Ob0', '/Od', '/RTC1'] msvc_optimization_args = { - '0': [], + '0': ['/Od', '/Ob0'], 'g': ['/O0'], '1': ['/O1'], - '2': ['/O2'], - '3': ['/O2'], - 's': ['/O1'], # Implies /Os. + '2': ['/O2', '/Ob1'], + '3': ['/O2', '/Ob2', '/Gw'], + 's': ['/O1', '/Gw'], # Implies /Os. } # type: T.Dict[str, T.List[str]] msvc_debug_args = { False: [], - True: [] # Fixme! + True: ['/Zi'] } # type: T.Dict[bool, T.List[str]] @@ -182,12 +182,18 @@ class VisualStudioLikeCompiler(Compiler, metaclass=abc.ABCMeta): return ['/Fe' + target] return ['/Fo' + target] - def get_optimization_args(self, optimization_level: str) -> T.List[str]: - return msvc_optimization_args[optimization_level] + def get_buildtype_args(self, buildtype: str) -> T.List[str]: + return msvc_buildtype_args[buildtype] def get_debug_args(self, is_debug: bool) -> T.List[str]: return msvc_debug_args[is_debug] + def get_optimization_args(self, optimization_level: str) -> T.List[str]: + args = msvc_optimization_args[optimization_level] + if mesonlib.version_compare(self.version, '<18.0'): + args = [arg for arg in args if arg != '/Gw'] + return args + def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]: return [] @@ -409,12 +415,6 @@ class MSVCCompiler(VisualStudioLikeCompiler): return None return super().get_instruction_set_args(instruction_set) - def get_buildtype_args(self, buildtype: str) -> T.List[str]: - args = msvc_buildtype_args[buildtype] - if mesonlib.version_compare(self.version, '<18.0'): - args = [arg for arg in args if arg != '/Gw'] - return args - def get_pch_base_name(self, header: str) -> str: return os.path.basename(header) diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index c3d2284..7b2f2d9 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -2052,26 +2052,26 @@ class ExternalProgram: def get_command(self) -> T.List[str]: return self.command[:] - def get_path(self): + def get_path(self) -> str: return self.path - def get_name(self): + def get_name(self) -> str: return self.name class NonExistingExternalProgram(ExternalProgram): # lgtm [py/missing-call-to-init] "A program that will never exist" - def __init__(self, name='nonexistingprogram'): + def __init__(self, name: str = 'nonexistingprogram') -> None: self.name = name self.command = [None] self.path = None - def __repr__(self): + def __repr__(self) -> str: r = '<{} {!r} -> {!r}>' return r.format(self.__class__.__name__, self.name, self.command) - def found(self): + def found(self) -> bool: return False diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 3af5b51..0e5e0f8 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -1924,6 +1924,8 @@ class MesonMain(InterpreterObject): 'current_build_dir': self.current_build_dir_method, 'source_root': self.source_root_method, 'build_root': self.build_root_method, + 'project_source_root': self.project_source_root_method, + 'project_build_root': self.project_build_root_method, 'add_install_script': self.add_install_script_method, 'add_postconf_script': self.add_postconf_script_method, 'add_dist_script': self.add_dist_script_method, @@ -2061,16 +2063,38 @@ class MesonMain(InterpreterObject): @noPosargs @permittedKwargs({}) + @FeatureDeprecated('meson.source_root', '0.56.0', 'use meson.current_source_dir instead.') def source_root_method(self, args, kwargs): return self.interpreter.environment.source_dir @noPosargs @permittedKwargs({}) + @FeatureDeprecated('meson.build_root', '0.56.0', 'use meson.current_build_dir instead.') def build_root_method(self, args, kwargs): return self.interpreter.environment.build_dir @noPosargs @permittedKwargs({}) + @FeatureNew('meson.project_source_root', '0.56.0') + def project_source_root_method(self, args, kwargs): + src = self.interpreter.environment.source_dir + sub = self.interpreter.root_subdir + if sub == '': + return src + return os.path.join(src, sub) + + @noPosargs + @permittedKwargs({}) + @FeatureNew('meson.project_build_root', '0.56.0') + def project_build_root_method(self, args, kwargs): + src = self.interpreter.environment.build_dir + sub = self.interpreter.root_subdir + if sub == '': + return src + return os.path.join(src, sub) + + @noPosargs + @permittedKwargs({}) @FeatureDeprecated('meson.has_exe_wrapper', '0.55.0', 'use meson.can_run_host_binaries instead.') def has_exe_wrapper_method(self, args: T.Tuple[object, ...], kwargs: T.Dict[str, object]) -> bool: return self.can_run_host_binaries_impl(args, kwargs) diff --git a/mesonbuild/interpreterbase.py b/mesonbuild/interpreterbase.py index 1524409..d3f8181 100644 --- a/mesonbuild/interpreterbase.py +++ b/mesonbuild/interpreterbase.py @@ -464,6 +464,7 @@ class InterpreterBase: self.funcs = {} # type: T.Dict[str, T.Callable[[mparser.BaseNode, T.List[TYPE_nvar], T.Dict[str, TYPE_nvar]], TYPE_var]] self.builtin = {} # type: T.Dict[str, InterpreterObject] self.subdir = subdir + self.root_subdir = subdir self.subproject = subproject self.variables = {} # type: T.Dict[str, TYPE_var] self.argument_depth = 0 diff --git a/mesonbuild/mdist.py b/mesonbuild/mdist.py index fbda240..6985ca9 100644 --- a/mesonbuild/mdist.py +++ b/mesonbuild/mdist.py @@ -35,7 +35,7 @@ archive_extension = {'gztar': '.tar.gz', def add_arguments(parser): parser.add_argument('-C', default='.', dest='wd', help='directory to cd into before running') - parser.add_argument('--formats', default='xztar', + parser.add_argument('--formats', default='xztar', choices=archive_choices, help='Comma separated list of archive types to create.') parser.add_argument('--include-subprojects', action='store_true', help='Include source code of subprojects that have been used for the build.') diff --git a/mesonbuild/modules/windows.py b/mesonbuild/modules/windows.py index 6050705..a5faa32 100644 --- a/mesonbuild/modules/windows.py +++ b/mesonbuild/modules/windows.py @@ -119,22 +119,23 @@ class WindowsModule(ExtensionModule): src = unholder(src) if isinstance(src, str): - name_format = 'file {!r}' + name_formatted = src name = os.path.join(state.subdir, src) elif isinstance(src, mesonlib.File): - name_format = 'file {!r}' + name_formatted = src.fname name = src.relative_name() elif isinstance(src, build.CustomTarget): if len(src.get_outputs()) > 1: raise MesonException('windows.compile_resources does not accept custom targets with more than 1 output.') - name_format = 'target {!r}' + name_formatted = src.get_filename() name = src.get_id() else: raise MesonException('Unexpected source type {!r}. windows.compile_resources accepts only strings, files, custom targets, and lists thereof.'.format(src)) # Path separators are not allowed in target names name = name.replace('/', '_').replace('\\', '_') + name_formatted = name_formatted.replace('/', '_').replace('\\', '_') res_kwargs = { 'output': name + '_@BASENAME@.' + suffix, @@ -149,7 +150,7 @@ class WindowsModule(ExtensionModule): res_kwargs['depfile'] = res_kwargs['output'] + '.d' res_kwargs['command'] += ['--preprocessor-arg=-MD', '--preprocessor-arg=-MQ@OUTPUT@', '--preprocessor-arg=-MF@DEPFILE@'] - res_targets.append(build.CustomTarget('Windows resource for ' + name_format.format(name), state.subdir, state.subproject, res_kwargs)) + res_targets.append(build.CustomTarget(name_formatted, state.subdir, state.subproject, res_kwargs)) add_target(args) diff --git a/mesonbuild/scripts/meson_exe.py b/mesonbuild/scripts/meson_exe.py index df54b47..50ad2f5 100644 --- a/mesonbuild/scripts/meson_exe.py +++ b/mesonbuild/scripts/meson_exe.py @@ -55,9 +55,12 @@ def run_exe(exe: ExecutableSerialisation) -> int: stderr=subprocess.PIPE) stdout, stderr = p.communicate() + if exe.pickled and p.returncode != 0: + print('while executing {!r}'.format(cmd_args)) + if p.returncode == 0xc0000135: # STATUS_DLL_NOT_FOUND on Windows indicating a common problem that is otherwise hard to diagnose - raise FileNotFoundError('Missing DLLs on calling {!r}'.format(cmd_args)) + raise FileNotFoundError('due to missing DLLs') if exe.capture and p.returncode == 0: skip_write = False @@ -90,6 +93,7 @@ def run(args: T.List[str]) -> int: parser.error('no other arguments can be used with --unpickle') with open(options.unpickle, 'rb') as f: exe = pickle.load(f) + exe.pickled = True else: exe = ExecutableSerialisation(cmd_args, capture=options.capture) diff --git a/run_mypy.py b/run_mypy.py index db38112..04f563a 100755 --- a/run_mypy.py +++ b/run_mypy.py @@ -7,64 +7,70 @@ from pathlib import Path import typing as T modules = [ - # fully typed submodules - 'mesonbuild/ast', - 'mesonbuild/compilers/mixins', - 'mesonbuild/scripts', - 'mesonbuild/wrap', + # fully typed submodules + 'mesonbuild/ast', + 'mesonbuild/cmake', + 'mesonbuild/compilers/mixins', + 'mesonbuild/scripts', + 'mesonbuild/wrap', - # specific files - 'mesonbuild/arglist.py', - 'mesonbuild/compilers/compilers.py', - 'mesonbuild/compilers/c_function_attributes.py', - 'mesonbuild/compilers/objc.py', - 'mesonbuild/compilers/objcpp.py', - # 'mesonbuild/coredata.py', - 'mesonbuild/dependencies/boost.py', - 'mesonbuild/dependencies/hdf5.py', - 'mesonbuild/dependencies/mpi.py', - 'mesonbuild/envconfig.py', - 'mesonbuild/interpreterbase.py', - 'mesonbuild/linkers.py', - 'mesonbuild/mcompile.py', - 'mesonbuild/mesonlib.py', - 'mesonbuild/minit.py', - 'mesonbuild/mintro.py', - 'mesonbuild/mlog.py', - 'mesonbuild/modules/fs.py', - 'mesonbuild/mparser.py', - 'mesonbuild/msetup.py', - 'mesonbuild/mtest.py', + # specific files + 'mesonbuild/arglist.py', + 'mesonbuild/compilers/compilers.py', + 'mesonbuild/compilers/c_function_attributes.py', + 'mesonbuild/compilers/objc.py', + 'mesonbuild/compilers/objcpp.py', + # 'mesonbuild/coredata.py', + 'mesonbuild/dependencies/boost.py', + 'mesonbuild/dependencies/hdf5.py', + 'mesonbuild/dependencies/mpi.py', + 'mesonbuild/envconfig.py', + 'mesonbuild/interpreterbase.py', + 'mesonbuild/linkers.py', + 'mesonbuild/mcompile.py', + 'mesonbuild/mesonlib.py', + 'mesonbuild/minit.py', + 'mesonbuild/mintro.py', + 'mesonbuild/mlog.py', + 'mesonbuild/modules/fs.py', + 'mesonbuild/mparser.py', + 'mesonbuild/msetup.py', + 'mesonbuild/mtest.py', - 'run_mypy.py', - 'tools' + 'run_mypy.py', + 'tools' ] def check_mypy() -> None: - try: - import mypy - except ImportError: - print('Failed import mypy') - sys.exit(1) + try: + import mypy + except ImportError: + print('Failed import mypy') + sys.exit(1) def main() -> int: - check_mypy() + check_mypy() - root = Path(__file__).absolute().parent - args = [] # type: T.List[str] + root = Path(__file__).absolute().parent + args = [] # type: T.List[str] - parser = argparse.ArgumentParser(description='Process some integers.') - parser.add_argument('-p', '--pretty', action='store_true', help='pretty print mypy errors') + parser = argparse.ArgumentParser(description='Process some integers.') + parser.add_argument('-p', '--pretty', action='store_true', help='pretty print mypy errors') + parser.add_argument('-C', '--clear', action='store_true', help='clear the terminal before running mypy') - opts = parser.parse_args() - if opts.pretty: - args.append('--pretty') + opts = parser.parse_args() + if opts.pretty: + args.append('--pretty') - p = subprocess.run( - [sys.executable, '-m', 'mypy'] + args + modules, - cwd=root, - ) - return p.returncode + if opts.clear: + print('\x1bc', end='', flush=True) + + print('Running mypy (this can take some time) ...') + p = subprocess.run( + [sys.executable, '-m', 'mypy'] + args + modules, + cwd=root, + ) + return p.returncode if __name__ == '__main__': - sys.exit(main()) + sys.exit(main()) diff --git a/test cases/common/227 fs module/meson.build b/test cases/common/227 fs module/meson.build index cff0987..f090d35 100644 --- a/test cases/common/227 fs module/meson.build +++ b/test cases/common/227 fs module/meson.build @@ -96,6 +96,9 @@ f1 = 'meson.build' f2 = 'subdir/../meson.build' assert(fs.is_samepath(f1, f2), 'is_samepath not detercting same files') assert(fs.is_samepath(meson.source_root(), 'subdir/..'), 'is_samepath not detecting same directory') +assert(fs.is_samepath(meson.project_source_root(), 'subdir/..'), 'is_samepath not detecting same directory') +# This fails with python3.5. It can be uncommented when we depend on python >= 3.6 +#assert(fs.is_samepath(meson.project_build_root(), meson.current_build_dir() / 'subdir/..'), 'is_samepath not detecting same directory') assert(not fs.is_samepath(f1, 'subdir/subdirfile.txt'), 'is_samepath known bad comparison') assert(not fs.is_samepath('not-a-path', f2), 'is_samepath should not error if path(s) do not exist') @@ -111,3 +114,5 @@ assert(fs.stem('foo/bar/baz.dll') == 'baz', 'failed to get stem with suffix') assert(fs.stem('foo/bar/baz.dll.a') == 'baz.dll', 'failed to get stem with compound suffix') subdir('subdir') + +subproject('subbie') diff --git a/test cases/common/227 fs module/subdir/meson.build b/test cases/common/227 fs module/subdir/meson.build index ec6f102..dc04b41 100644 --- a/test cases/common/227 fs module/subdir/meson.build +++ b/test cases/common/227 fs module/subdir/meson.build @@ -1 +1,3 @@ assert(fs.exists('subdirfile.txt'), 'Subdir file lookup is broken.') +assert(fs.is_samepath(meson.project_source_root(), '..'), 'is_samepath not detecting same directory') +assert(fs.is_samepath(meson.project_build_root(), meson.current_build_dir() / '..'), 'is_samepath not detecting same directory') diff --git a/test cases/common/227 fs module/subprojects/subbie/meson.build b/test cases/common/227 fs module/subprojects/subbie/meson.build index 55fc286..ca6d36b 100644 --- a/test cases/common/227 fs module/subprojects/subbie/meson.build +++ b/test cases/common/227 fs module/subprojects/subbie/meson.build @@ -3,7 +3,7 @@ project('subbie') fs = import('fs') assert(fs.exists('subprojectfile.txt'), 'Subproject root file not found.') +assert(fs.is_samepath(meson.project_source_root(), meson.current_source_dir()), 'is_samepath not detecting same directory') +assert(fs.is_samepath(meson.project_build_root(), meson.current_build_dir()), 'is_samepath not detecting same directory') subdir('subsub') - -subproject('subbie') diff --git a/test cases/common/227 fs module/subprojects/subbie/subsub/meson.build b/test cases/common/227 fs module/subprojects/subbie/subsub/meson.build index cf9a271..4ac68ae 100644 --- a/test cases/common/227 fs module/subprojects/subbie/subsub/meson.build +++ b/test cases/common/227 fs module/subprojects/subbie/subsub/meson.build @@ -1 +1,3 @@ assert(fs.exists('subsubfile.txt'), 'Subproject subdir lookup failed.') +assert(fs.is_samepath(meson.project_source_root(), meson.current_source_dir() / '..'), 'is_samepath not detecting same directory') +assert(fs.is_samepath(meson.project_build_root(), meson.current_build_dir() / '..'), 'is_samepath not detecting same directory') diff --git a/test cases/failing build/5 failed pickled/meson.build b/test cases/failing build/5 failed pickled/meson.build new file mode 100644 index 0000000..28115d5 --- /dev/null +++ b/test cases/failing build/5 failed pickled/meson.build @@ -0,0 +1,7 @@ +project('failed pickled command') + +custom_target('failure', + command: ['false', '\n'], + output: 'output.txt', + build_by_default: true, +) diff --git a/test cases/frameworks/1 boost/meson.build b/test cases/frameworks/1 boost/meson.build index 5a2e1a1..83570f0 100644 --- a/test cases/frameworks/1 boost/meson.build +++ b/test cases/frameworks/1 boost/meson.build @@ -51,8 +51,8 @@ extralibexe = executable('extralibexe', 'extralib.cpp', dependencies : extralibd python2module = shared_library('python2_module', ['python_module.cpp'], dependencies: [python2dep, bpython2dep], name_prefix: '', cpp_args: ['-DMOD_NAME=python2_module']) python3module = shared_library('python3_module', ['python_module.cpp'], dependencies: [python3dep, bpython3dep], name_prefix: '', cpp_args: ['-DMOD_NAME=python3_module']) -test('Boost linktest', linkexe) -test('Boost UTF test', unitexe) +test('Boost linktest', linkexe, timeout: 60) +test('Boost UTF test', unitexe, timeout: 60) test('Boost nomod', nomodexe) if host_machine.system() != 'darwin' or s # Segfaults on macOS with dynamic linking since Boost 1.73 |