1414from colcon_cmake .task .cmake import get_variable_from_cmake_cache
1515from colcon_cmake .task .cmake import get_visual_studio_version
1616from colcon_cmake .task .cmake import has_target
17+ from colcon_cmake .task .cmake import is_jobs_base_generator
1718from colcon_cmake .task .cmake import is_multi_configuration_generator
1819from colcon_core .environment import create_environment_scripts
1920from colcon_core .logging import colcon_logger
@@ -63,6 +64,12 @@ def add_arguments(self, *, parser): # noqa: D102
6364 '--cmake-force-configure' ,
6465 action = 'store_true' ,
6566 help = 'Force CMake configure step' )
67+ parser .add_argument (
68+ '--cmake-jobs' ,
69+ type = int ,
70+ help = 'Number of jobs to use for supported generators (e.g., Ninja '
71+ 'Makefiles). Negative values subtract from the maximum '
72+ 'available, so --jobs=-1 uses all bar 1 available threads.' )
6673
6774 async def build ( # noqa: D102
6875 self , * , additional_hooks = None , skip_hook_creation = False ,
@@ -221,6 +228,8 @@ async def _build(self, args, env, *, additional_targets=None):
221228 if additional_targets :
222229 targets += additional_targets
223230
231+ jobs_base_generator = is_jobs_base_generator (
232+ args .build_base , args .cmake_args )
224233 multi_configuration_generator = is_multi_configuration_generator (
225234 args .build_base , args .cmake_args )
226235 if multi_configuration_generator :
@@ -240,8 +249,8 @@ async def _build(self, args, env, *, additional_targets=None):
240249 cmd += ['--clean-first' ]
241250 if multi_configuration_generator :
242251 cmd += ['--config' , self ._get_configuration (args )]
243- else :
244- job_args = self ._get_make_arguments ( env )
252+ if jobs_base_generator :
253+ job_args = self ._get_make_jobs_arguments ( args , env )
245254 if job_args :
246255 cmd += ['--' ] + job_args
247256 completed = await run (
@@ -281,7 +290,7 @@ def _get_msbuild_environment(self, args, env):
281290 env ['CL' ] = ' ' .join (cl_split )
282291 return env
283292
284- def _get_make_arguments (self , env ):
293+ def _get_make_jobs_arguments (self , args , env ):
285294 """
286295 Get the make arguments to limit the number of simultaneously run jobs.
287296
@@ -291,28 +300,37 @@ def _get_make_arguments(self, env):
291300 :returns: list of make arguments
292301 :rtype: list of strings
293302 """
294- # check MAKEFLAGS for -j/--jobs/-l/--load-average arguments
295- makeflags = env .get ('MAKEFLAGS' , '' )
296- regex = (
297- r'(?:^|\s)'
298- r'(-?(?:j|l)(?:\s*[0-9]+|\s|$))'
299- r'|'
300- r'(?:^|\s)'
301- r'((?:--)?(?:jobs|load-average)(?:(?:=|\s+)[0-9]+|(?:\s|$)))'
302- )
303- matches = re .findall (regex , makeflags ) or []
304- matches = [m [0 ] or m [1 ] for m in matches ]
305- if matches :
306- # do not extend make arguments, let MAKEFLAGS set things
307- return []
308- # Use the number of CPU cores
309- jobs = os .cpu_count ()
310- with suppress (AttributeError ):
311- # consider restricted set of CPUs if applicable
312- jobs = min (jobs , len (os .sched_getaffinity (0 )))
313- if jobs is None :
314- # the number of cores can't be determined
315- return []
303+ generator = get_generator (args .build_base )
304+ if "Makefiles" in generator and args .cmake_jobs is None :
305+ # check MAKEFLAGS for -j/--jobs/-l/--load-average arguments
306+ # Note: Ninja does not support environment variables.
307+ makeflags = env .get ('MAKEFLAGS' , '' )
308+ regex = (
309+ r'(?:^|\s)'
310+ r'(-?(?:j|l)(?:\s*[0-9]+|\s|$))'
311+ r'|'
312+ r'(?:^|\s)'
313+ r'((?:--)?(?:jobs|load-average)(?:(?:=|\s+)[0-9]+|(?:\s|$)))'
314+ )
315+ matches = re .findall (regex , makeflags ) or []
316+ matches = [m [0 ] or m [1 ] for m in matches ]
317+ if matches :
318+ # do not extend make arguments, let MAKEFLAGS set things
319+ return []
320+ # Use command line specified jobs if positive.
321+ jobs_args = args .cmake_jobs if args .cmake_jobs is not None else 0
322+ jobs = 0
323+ if jobs_args <= 0 :
324+ # Base off the number of CPU cores if jobs arg non-positive.
325+ jobs = os .cpu_count ()
326+ with suppress (AttributeError ):
327+ # consider restricted set of CPUs if applicable
328+ jobs = min (jobs , len (os .sched_getaffinity (0 )))
329+ if jobs is None :
330+ # the number of cores can't be determined
331+ return []
332+ # Finalise jobs as as CPU count deducting the limit specified.
333+ jobs = max (jobs + jobs_args , 1 )
316334 return [
317335 '-j{jobs}' .format_map (locals ()),
318336 '-l{jobs}' .format_map (locals ()),
@@ -325,7 +343,8 @@ async def _install(self, args, env):
325343 raise RuntimeError ("Could not find 'cmake' executable" )
326344 cmd = [CMAKE_EXECUTABLE ]
327345 cmake_ver = get_cmake_version ()
328- allow_job_args = True
346+ allow_job_args = is_jobs_base_generator (
347+ args .build_base , args .cmake_args )
329348 if cmake_ver and cmake_ver >= parse_version ('3.15.0' ):
330349 # CMake 3.15+ supports invoking `cmake --install`
331350 cmd += ['--install' , args .build_base ]
@@ -343,8 +362,8 @@ async def _install(self, args, env):
343362 args .build_base , args .cmake_args )
344363 if multi_configuration_generator :
345364 cmd += ['--config' , self ._get_configuration (args )]
346- elif allow_job_args :
347- job_args = self ._get_make_arguments ( env )
365+ if allow_job_args :
366+ job_args = self ._get_make_jobs_arguments ( args , env )
348367 if job_args :
349368 cmd += ['--' ] + job_args
350369 return await run (
0 commit comments