Is there a builtin method to create a target that's only run if dependent files are changed/touched?

Hey. I looked everywhere for this and couldn’t find anything. So basically I’m looking for a way to create a target that is dependent on existing files. For example, let’s say I have a bunch of python files in my repo and I want to create a target named lint_python_files that runs pylint on them. If I run the target once it should lint all python files. If I run it again right after, it should know that none of the python files have changed and just skip linting entirely. If I then touch/change a single python file and then run the target, then I’d like only that file to be linted. I have created a custom function that does this, but was wondering if there’s a smarter way to do this or if there’s a builtin function for this?

function(add_glob_targets)
  cmake_parse_arguments(SMART
    ""
    "TARGET;COMMAND"
    "FLAGS;FILES"
    ${ARGN}
  )

  set(touch_dir ${TOUCHES_DIR}/${SMART_TARGET})
  file(MAKE_DIRECTORY ${touch_dir})

  foreach(f ${SMART_FILES})
    string(REPLACE "/" "_" filename ${f})
    set(touch_file ${touch_dir}/${filename})
    add_custom_command(
      OUTPUT ${touch_file}
      COMMAND ${CMAKE_COMMAND} -E touch ${touch_file}
      COMMAND ${PRG} ${SMART_FLAGS} ${f}
      WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
      DEPENDS ${f})

    list(APPEND touch_list ${touch_file})
  endforeach()

  add_custom_target(${SMART_TARGET} DEPENDS ${touch_list})
endfunction()
1 Like

You want a combination of add_custom_command and add_custom_target:

add_custom_command(
  OUTPUT lint_python_files.stamp
  COMMAND pylint ${python_files}
  COMMAND ${CMAKE_COMMAND} -E touch lint_python_files.stamp
  DEPENDS ${python_files}
  )
add_custom_target(lint_python_files ALL
  DEPENDS lint_python_files.stamp
  )

The lint custom command will create a stamp file to indicate that it’s been run, and will be re-run any time the input files change.

Hmm, wouldn’t this lint all python files even if only one file was changed? Haven’t tested it yet, will give it a try.

In that case you want something like this:

set(pylint_stamp_files)
foreach(file IN LISTS python_files)
  set(stamp_file ${file}.pylint_stamp)
  list(APPEND pylint_stamp_files ${stamp_file})
  add_custom_command(
    OUTPUT ${stamp_file}
    COMMAND pylint ${file}
    COMMAND ${CMAKE_COMMAND} -E touch ${stamp_file}
    DEPENDS ${file}
    )
endforeach()

add_custom_target(lint_python_files ALL
  DEPENDS ${pylint_stamp_files}
  )

This will create a stamp file for each Python file.

Sure, but that’s basically what I wrote as the function I use right now in my post.

Indeed it is… sorry about that. Yeah, you’re already doing it right.

1 Like