diff --git a/bin/catkin_make b/bin/catkin_make index 6b2d1efd9..9b1a30701 100755 --- a/bin/catkin_make +++ b/bin/catkin_make @@ -133,11 +133,22 @@ def main(): print(fmt('@{yf}Packages @{boldon}"%s"@{boldoff} not found in the workspace - ignoring them' % ', '.join(sorted(unknown_packages))), file=sys.stderr) args.pkg = [name for name in args.pkg if name in packages_by_name] + if not [arg for arg in cmake_args if arg.startswith('-G')]: + if not args.use_ninja: + cmake_args += ['-G', 'Unix Makefiles'] + else: + cmake_args += ['-G', 'Ninja'] + elif args.use_ninja: + return fmt("@{rf}Error: either specify a generator using '-G...' or '--use-ninja' but not both") + # check if cmake must be run (either for a changed list of package paths or changed cmake arguments) force_cmake = cmake_input_changed(packages, build_path, cmake_args=cmake_args) # consider calling cmake - makefile = os.path.join(build_path, 'Makefile') + if not args.use_ninja: + makefile = os.path.join(build_path, 'Makefile') + else: + makefile = os.path.join(build_path, 'build.ninja') if not os.path.exists(makefile) or args.force_cmake or force_cmake: cmd = [ 'cmake', @@ -155,7 +166,10 @@ def main(): except subprocess.CalledProcessError: return fmt('@{rf}Invoking @{boldon}"cmake"@{boldoff} failed') else: - cmd = ['make', 'cmake_check_build_system'] + if not args.use_ninja: + cmd = ['make', 'cmake_check_build_system'] + else: + cmd = ['ninja', 'build.ninja'] try: print_command_banner(cmd, build_path, color=not args.no_color) if args.no_color: @@ -163,12 +177,15 @@ def main(): else: run_command_colorized(cmd, build_path) except subprocess.CalledProcessError: - return fmt('@{rf}Invoking @{boldon}"make cmake_check_build_system"@{boldoff} failed') + return fmt('@{rf}Invoking @{boldon}"%s"@{boldoff} failed' % ' '.join(cmd)) ensure_workspace_marker(base_path) # invoke make - cmd = ['make'] + if not args.use_ninja: + cmd = ['make'] + else: + cmd = ['ninja'] cmd.extend(handle_make_arguments(args.make_args)) try: if not args.pkg: @@ -179,7 +196,7 @@ def main(): print_command_banner(cmd, make_path, color=not args.no_color) run_command(cmd, make_path) except subprocess.CalledProcessError: - return fmt('@{rf}Invoking @{boldon}"make"@{boldoff} failed') + return fmt('@{rf}Invoking @{boldon}"%s"@{boldoff} failed' % ' '.join(cmd)) def _parse_args(args=sys.argv[1:]): @@ -202,6 +219,7 @@ def _parse_args(args=sys.argv[1:]): add('-C', '--directory', default=os.curdir, help="The base path of the workspace (default '%s')" % os.curdir) add('--source', help="The path to the source space (default 'workspace_base/src')") add('--build', help="The path to the build space (default 'workspace_base/build')") + add('--use-ninja', action='store_true', help="Use 'ninja' instead of 'make'") add('--force-cmake', action='store_true', help="Invoke 'cmake' even if it has been executed before") add('--no-color', action='store_true', help='Disables colored output (only for catkin_make and CMake)') add('--pkg', nargs='+', help="Invoke 'make' on specific packages only") diff --git a/bin/catkin_make_isolated b/bin/catkin_make_isolated index e4c11a4d6..606f22d89 100755 --- a/bin/catkin_make_isolated +++ b/bin/catkin_make_isolated @@ -45,6 +45,7 @@ def parse_args(args=None): help='Build each catkin package into a common devel space.') add('--install-space', default=None, help="Sets the target install space (default 'workspace_base/install_isolated')") + add('--use-ninja', action='store_true', help="Use 'ninja' instead of 'make'") add('--install', action='store_true', default=False, help='Causes each catkin package to be installed.') add('--force-cmake', action='store_true', default=False, @@ -142,7 +143,8 @@ def main(): catkin_make_args=opts.catkin_make_args, continue_from_pkg=opts.from_package is not None, only_pkg_with_deps=opts.only_pkg_with_deps, - destdir=destdir + destdir=destdir, + use_ninja=opts.use_ninja ) if __name__ == '__main__': diff --git a/cmake/env-hooks/05.catkin_make.bash b/cmake/env-hooks/05.catkin_make.bash index bb6ab445b..621d668a4 100644 --- a/cmake/env-hooks/05.catkin_make.bash +++ b/cmake/env-hooks/05.catkin_make.bash @@ -58,6 +58,9 @@ function _catkin_make() if [ -f "$makefile_dir/Makefile" ]; then cur=${COMP_WORDS[COMP_CWORD]} COMPREPLY=( $( compgen -W "`make -C $makefile_dir -qp 2>/dev/null | awk -F':' '/^[a-zA-Z0-9][a-zA-Z0-9_\.]*:/ { print $1 }'`" -- $cur )) + elif [ -f "$makefile_dir/build.ninja" ]; then + cur=${COMP_WORDS[COMP_CWORD]} + COMPREPLY=( $( compgen -W "`ninja -C $makefile_dir -t targets 2>/dev/null | awk -F':' '/^[a-zA-Z0-9][a-zA-Z0-9_\.]*:/ { print $1 }'`" -- $cur )) fi fi } && diff --git a/cmake/env-hooks/05.catkin_make_isolated.bash b/cmake/env-hooks/05.catkin_make_isolated.bash index 5cdc4dd88..99e5d3016 100644 --- a/cmake/env-hooks/05.catkin_make_isolated.bash +++ b/cmake/env-hooks/05.catkin_make_isolated.bash @@ -56,6 +56,9 @@ function _catkin_make_isolated() if [ -f "$makefile_dir/Makefile" ]; then cur=${COMP_WORDS[COMP_CWORD]} COMPREPLY=( $( compgen -W "`make -C $makefile_dir -qp 2>/dev/null | awk -F':' '/^[a-zA-Z0-9][a-zA-Z0-9_\.]*:/ { print $1 }'`" -- $cur )) + elif [ -f "$makefile_dir/build.ninja" ]; then + cur=${COMP_WORDS[COMP_CWORD]} + COMPREPLY=( $( compgen -W "`ninja -C $makefile_dir -t targets 2>/dev/null | awk -F':' '/^[a-zA-Z0-9][a-zA-Z0-9_\.]*:/ { print $1 }'`" -- $cur )) fi fi } && diff --git a/python/catkin/builder.py b/python/catkin/builder.py index faa3cf72f..79c56245f 100644 --- a/python/catkin/builder.py +++ b/python/catkin/builder.py @@ -334,7 +334,7 @@ def build_catkin_package( path, package, workspace, buildspace, develspace, installspace, install, force_cmake, quiet, last_env, cmake_args, make_args, - destdir=None + destdir=None, use_ninja=False ): cprint( "Processing @{cf}catkin@| package: '@!@{bf}" + @@ -352,7 +352,11 @@ def build_catkin_package( ) # Check for Makefile and maybe call cmake - makefile = os.path.join(build_dir, 'Makefile') + if not use_ninja: + makefile_name = 'Makefile' + else: + makefile_name = 'build.ninja' + makefile = os.path.join(build_dir, makefile_name) if not os.path.exists(makefile) or force_cmake: package_dir = os.path.dirname(package.filename) if not os.path.exists(os.path.join(package_dir, 'CMakeLists.txt')): @@ -390,9 +394,12 @@ def build_catkin_package( os.remove(makefile) raise else: - print('Makefile exists, skipping explicit cmake invocation...') + print('%s exists, skipping explicit cmake invocation...' % makefile_name) # Check to see if cmake needs to be run via make - make_check_cmake_cmd = ['make', 'cmake_check_build_system'] + if not use_ninja: + make_check_cmake_cmd = ['make', 'cmake_check_build_system'] + else: + make_check_cmake_cmd = ['ninja', 'build.ninja'] add_env = get_additional_environment(install, destdir, installspace) isolation_print_command(' '.join(make_check_cmake_cmd), build_dir, add_env=add_env) if last_env is not None: @@ -402,7 +409,11 @@ def build_catkin_package( ) # Run make - make_cmd = ['make'] + if not use_ninja: + make_executable = 'make' + else: + make_executable = 'ninja' + make_cmd = [make_executable] make_cmd.extend(handle_make_arguments(make_args)) isolation_print_command(' '.join(make_cmd), build_dir) if last_env is not None: @@ -411,19 +422,25 @@ def build_catkin_package( # Make install if install: - if has_make_target(build_dir, 'install'): - make_install_cmd = ['make', 'install'] + if has_make_target(build_dir, 'install', use_ninja=use_ninja): + make_install_cmd = [make_executable, 'install'] isolation_print_command(' '.join(make_install_cmd), build_dir) if last_env is not None: make_install_cmd = [last_env] + make_install_cmd run_command(make_install_cmd, build_dir, quiet) else: - print(fmt('@{yf}Package has no "@{boldon}install@{boldoff}" target, skipping "make install" invocation...')) + print(fmt('@{yf}Package has no "@{boldon}install@{boldoff}" target, skipping "%s install" invocation...' % make_executable)) -def has_make_target(path, target): - output = run_command(['make', '-pn'], path, quiet=True) +def has_make_target(path, target, use_ninja=False): + if not use_ninja: + output = run_command(['make', '-pn'], path, quiet=True) + else: + output = run_command(['ninja', '-t', 'targets'], path, quiet=True) lines = output.splitlines() + # strip nanja warnings since they look similar to targets + if use_ninja: + lines = [l for l in lines if not l.startswith('ninja: warning:')] targets = [m.group(1) for m in [re.match('^([a-zA-Z0-9][a-zA-Z0-9_\.]*):', l) for l in lines] if m] return target in targets @@ -439,7 +456,7 @@ def build_cmake_package( path, package, workspace, buildspace, develspace, installspace, install, force_cmake, quiet, last_env, cmake_args, make_args, - destdir=None + destdir=None, use_ninja=False ): # Notify the user that we are processing a plain cmake package cprint( @@ -460,7 +477,11 @@ def build_cmake_package( "'{0}'".format(last_env)) # Check for Makefile and maybe call cmake - makefile = os.path.join(build_dir, 'Makefile') + if not use_ninja: + makefile_name = 'Makefile' + else: + makefile_name = 'build.ninja' + makefile = os.path.join(build_dir, makefile_name) install_target = installspace if install else develspace if not os.path.exists(makefile) or force_cmake: # Call cmake @@ -475,9 +496,12 @@ def build_cmake_package( cmake_cmd = [last_env] + cmake_cmd run_command_colorized(cmake_cmd, build_dir, quiet) else: - print('Makefile exists, skipping explicit cmake invocation...') + print('%s exists, skipping explicit cmake invocation...' % makefile_name) # Check to see if cmake needs to be run via make - make_check_cmake_cmd = ['make', 'cmake_check_build_system'] + if not use_ninja: + make_check_cmake_cmd = ['make', 'cmake_check_build_system'] + else: + make_check_cmake_cmd = ['ninja', 'build.ninja'] isolation_print_command(' '.join(make_check_cmake_cmd), build_dir) if last_env is not None: make_check_cmake_cmd = [last_env] + make_check_cmake_cmd @@ -486,7 +510,11 @@ def build_cmake_package( ) # Run make - make_cmd = ['make'] + if not use_ninja: + make_executable = 'make' + else: + make_executable = 'ninja' + make_cmd = [make_executable] make_cmd.extend(handle_make_arguments(make_args)) isolation_print_command(' '.join(make_cmd), build_dir) if last_env is not None: @@ -497,7 +525,7 @@ def build_cmake_package( run_command(make_cmd, build_dir, quiet, add_env={'DESTDIR': ''}) # Make install - make_install_cmd = ['make', 'install'] + make_install_cmd = [make_executable, 'install'] isolation_print_command(' '.join(make_install_cmd), build_dir) if last_env is not None: make_install_cmd = [last_env] + make_install_cmd @@ -610,7 +638,7 @@ def build_package( path, package, workspace, buildspace, develspace, installspace, install, force_cmake, quiet, last_env, cmake_args, make_args, catkin_make_args, - destdir=None, + destdir=None, use_ninja=False, number=None, of=None ): if platform.system() in ['Linux', 'Darwin']: @@ -624,7 +652,7 @@ def build_package( path, package, workspace, buildspace, develspace, installspace, install, force_cmake, quiet, last_env, cmake_args, make_args + catkin_make_args, - destdir=destdir + destdir=destdir, use_ninja=use_ninja ) if not os.path.exists(new_last_env): raise RuntimeError( @@ -638,7 +666,7 @@ def build_package( path, package, workspace, buildspace, develspace, installspace, install, force_cmake, quiet, last_env, cmake_args, make_args, - destdir=destdir + destdir=destdir, use_ninja=use_ninja ) else: sys.exit('Can not build package with unknown build_type') @@ -699,7 +727,8 @@ def build_workspace_isolated( catkin_make_args=None, continue_from_pkg=False, only_pkg_with_deps=None, - destdir=None + destdir=None, + use_ninja=False ): ''' Runs ``cmake``, ``make`` and optionally ``make install`` for all @@ -734,6 +763,7 @@ def build_workspace_isolated( recursive dependencies and ignore all other packages in the workspace, ``[str]`` :param destdir: define DESTDIR for cmake/invocation, ``string`` + :param use_ninja: if True, use ninja instead of make, ``bool`` ''' if not colorize: disable_ANSI_colors() @@ -773,6 +803,15 @@ def build_workspace_isolated( else: cmake_args = [] + if not [arg for arg in cmake_args if arg.startswith('-G')]: + if not use_ninja: + cmake_args += ['-G', 'Unix Makefiles'] + else: + cmake_args += ['-G', 'Ninja'] + elif use_ninja: + print(colorize_line("Error: either specify a generator using '-G...' or '--use-ninja' but not both")) + sys.exit(1) + if make_args: print("Additional make Arguments: " + " ".join(make_args)) else: @@ -871,7 +910,7 @@ def build_workspace_isolated( workspace, buildspace, pkg_develspace, installspace, install, force_cmake, quiet, last_env, cmake_args, make_args, catkin_make_args, - destdir=destdir, + destdir=destdir, use_ninja=use_ninja, number=index + 1, of=len(ordered_packages) ) except subprocess.CalledProcessError as e: