[CMake] Re: emitting CMakeLists.txt for TRY_COMPILE considered harmful

Brandon J. Van Every bvanevery at gmail.com
Thu Nov 23 02:02:08 EST 2006


Maybe the following is a better explanation of the pitfalls of emitting 
a CMakeLists.txt.
This is taken from the current Chicken CMake build.

####################################################################
#  CHANGELOG                                                       #
####################################################################

# A distribution archive (aka a tarball) will have ChangeLog already.
# A Darcs repository tree will not, however.  We have to generate it.
#
# Note that as of Nov. 22nd, 2006, a Darcs package that understands
# Cygwin paths is not readily available.  It is possible to compile
# Darcs from Haskell sources, but that requires GHC, which can be
# difficult to get working.  The upshot is it's a PITA to access
# Darcs from Cygwin and hence to create a ChangeLog.  It's possible,
# but one has to proceed carefully.
#
# Different shells can cause Darcs to fail.  For instance, running a
# Windows native Darcs under a Cygwin shell can fail, because the
# Windows native Darcs doesn't understand Cygwin paths.  A workaround
# is to avoid issuing any Cygwin path to Darcs, and instead use a
# WORKING_DIRECTORY, so that CMake handles some of its own paths and
# not Darcs.
#
# In principle, if we need to use a Darcs command, we should test
# whether Darcs is available and actually works.  In practice, writing
# reliable tool tests in CMake 2.4.4 is painful.
#
# You cannot use EXECUTE_PROCESS to write a tool test.  It executes in
# CMake's environment, not the actual build environment.  For instance,
# let's say Darcs is available at the Windows command prompt.
# EXECUTE_PROCESS will say it works.  However, it won't actually work
# under Visual Studio, because VS doesn't typically receive all the paths
# that the command prompt does.
#
# To write a tool test, Kitware expects one to emit a trivial CMakeLists.txt
# to a temporary subdirectory, and then TRY_COMPILE it.  In practice, this
# approach is exceedingly fragile, due to quote consumption problems with
# FILE(WRITE ...) and with shells.
#
# What's really needed is an entirely different / better mechanism for
# tool testing.  Something that's exactly parallel to the code we'd
# write here at the toplevel, so that there are no weird extraneous
# considerations.

SET(CHANGELOG_FILE -NOTFOUND)
IF(EXISTS ${Chicken_SOURCE_DIR}/_darcs)
  FIND_PROGRAM(DARCS_EXE darcs)
  IF(DARCS_EXE)
    FILE(REMOVE_RECURSE ${CMAKE_CURRENT_BINARY_DIR}/try-darcs)

    # Note the need to escape any quotes that are part of the file output.
    # I cannot figure out how to get code emitted in a file to quote 
properly.
    # Consequently, we use a WORKING_DIRECTORY to duck the issue.
    #
    # Note that the \"${DARCS_EXE}\" quotes are necessary here, even though
    # they are not generally necessary in the toplevel CMakeLists.txt, i.e.
    # this file you're reading now.  At this level, whitespace is 
escaped, i.e.
    #   ${DARCS_EXE} = E:/Program\ Files/darcsdir-w32
    # But once emitted, the whitespace escapes are lost.  We get
    #   ${DARCS_EXE} = E:/Program Files/darcsdir-w32
    # and of course Cygwin dies, as E:/Program isn't a valid command.
    #
    # Possibly this emission code should be replaced with a CONFIGURE_FILE
    # template.  Or else substitutions should be performed with
    # STRING(CONFIGURE ...).  Anything to get the quote / escape problems
    # under control.

    FILE(WRITE ${CMAKE_CURRENT_BINARY_DIR}/try-darcs/CMakeLists.txt "
      PROJECT(try-darcs)
      ADD_CUSTOM_TARGET(try
        WORKING_DIRECTORY \"${Chicken_SOURCE_DIR}\"
        COMMAND \"${DARCS_EXE}\" changes --last=0
      )
    ")
    TRY_COMPILE(DARCS_WORKS
       ${CMAKE_CURRENT_BINARY_DIR}/try-darcs
       ${CMAKE_CURRENT_BINARY_DIR}/try-darcs
       try-darcs try
    )
    IF(DARCS_WORKS)
      SET(CHANGELOG_FILE ${CMAKE_CURRENT_BINARY_DIR}/ChangeLog)

      # Custom commands have the format:
      #
      #   COMMAND command1 [args...]
      #
      # command1 has to be a CMake path.  This is not documented in 
CMake 2.4.4.
      # Think of command1 as receiving "special interpretation" and not 
really
      # being a "custom" command, i.e. you're not free to do what you like.
      #
      # Also, if command1 is an absolute pathname, it must have whitespace
      # handled properly.  The results of CMake Find* commands always have
      # whitespace handled properly, but if you're homebrewing your own
      # pathnames, be careful.
      #
      # Other path statements in [args...] need to be paths that your
      # tool understands.  This means you'll need native paths, unless your
      # tool happens to like CMake paths.
      #
      # As of November 22, 2006, there is no Cygwin version of Darcs.
      # Instead, one typically has a Windows native version of
      # Darcs running under a Cygwin shell.  This Windows native Darcs
      # does not understand Cygwin paths, i.e. 
--repodir=/cygdrive/c/whatever
      # will fail.
      #
      # Ideally, we'd complete the implementation of WINDOWS_PATH and 
provide
      # proper paths.  Pragmatically, this is a PITA.  We adopt the 
expedient
      # of using a working directory so that we don't have to bother with
      # --repodir.
      #
      # stdout is redirected to the CHANGELOG_FILE.  Redirection is a shell
      # operation, so we need a path that the shell understands.  Generally,
      # keeping what a CMake comamnd1 needs, separate from what a tool 
needs,
      # separate from what a shell needs, is confusing.  But that's the 
drill.

      NATIVE_PATH(CHANGELOG_FILE NATIVE_CHANGELOG_FILE)

      # Always build the ChangeLog unconditionally.  It doesn't take long,
      # and we need to make sure it stays up to date.  There is no easy
      # way to determine if the Darcs repository has changed since we last
      # looked at it, so brute force is the workaround.

      ADD_CUSTOM_TARGET(darcs-changelog ALL
        COMMENT "Generating ${NATIVE_CHANGELOG_FILE} from Darcs repository."
        WORKING_DIRECTORY ${Chicken_SOURCE_DIR}
        COMMAND ${DARCS_EXE} changes > ${NATIVE_CHANGELOG_FILE}
      )
    ELSE(DARCS_WORKS)
      ECHO_TARGET(darcs-changelog "Darcs does not work.  Cannot create 
ChangeLog.")
    ENDIF(DARCS_WORKS)
  ELSE(DARCS_EXE)
    ECHO_TARGET(darcs-changelog "Cannot find Darcs.  Cannot create 
ChangeLog.")   
  ENDIF(DARCS_EXE)
ELSE(EXISTS ${Chicken_SOURCE_DIR}/_darcs)
  IF(EXISTS ${Chicken_SOURCE_DIR}/ChangeLog)
    SET(CHANGELOG_FILE ${Chicken_SOURCE_DIR}/ChangeLog)
    ECHO_TARGET(darcs-changelog "No Darcs repository.  ChangeLog already 
exists as part of archive distribution.")
  ELSE(EXISTS ${Chicken_SOURCE_DIR}/ChangeLog)
    ECHO_TARGET(darcs-changelog "No Darcs repository.  ChangeLog is 
missing from archive distribution.")
  ENDIF(EXISTS ${Chicken_SOURCE_DIR}/ChangeLog)
ENDIF(EXISTS ${Chicken_SOURCE_DIR}/_darcs)



Brandon J. Van Every wrote:
> Summary: emitting a CMakeLists.txt is never parallel with standard 
> coding practices in the toplevel CMakeLists.txt.  At a minimum, this 
> causes programmers to do everything 2 different ways.  In the likely 
> case, the emission fails because it is fragile as quotes are 
> consumed.  These kinds of problems can easily chew up a programmer's 
> entire day, as I did today.
>
> I need to test whether Darcs source control commands actually work in 
> build environments.  It's dicey under Windows because there's no 
> Cygwin version of Darcs, just a Windows native version.  That would 
> seem good for Windows people, but the Windows Command Prompt, Cygwin 
> shell, MSYS shell, and the Visual Studio shell are all distinct build 
> environments.  They don't share the same search paths, and they don't 
> understand each other's path conventions.  So, aiming the right path 
> at the right shell and tool is highly problematical.
>
> I have working code for this problem, targeted at CMake 2.4.3.  In 
> that version, I simply ducked all the problems.  I used 
> WORKING_DIRECTORY as much as possible so that I wouldn't have to use 
> the Darcs --repodir=E:\devel\src\chicken command option.  I resorted 
> to such workarounds after 3 days of head scratching.  The head 
> scratching did have a productive outcome, however: Brad implemented 
> the VERBATIM feature in reaction to my problem.  Unfortunately, then I 
> went into survival mode and was unable to allocate time to trying out 
> VERBATIM while CMake 2.4.4 was still in beta.
>
> Come CMake 2.4.4, I thought I would give VERBATIM a whirl.  Oddly, I 
> was unable to get it to do anything constructive.  I started having 
> success when I abandoned it, at least at the level of my topmost 
> CMakeLists.txt.  Code like the following works fine:
>
>      # NATIVE_PATH reverses slashes and adds quotes for Windows
>      NATIVE_PATH(Chicken_SOURCE_DIR NATIVE_CHICKEN_SOURCE_DIR)
>      NATIVE_PATH(CHANGELOG_FILE NATIVE_CHANGELOG_FILE)
>
>      ADD_CUSTOM_TARGET(darcs-changelog ALL
>        COMMENT "Generating ${NATIVE_CHANGELOG_FILE} from Darcs 
> repository."
>        COMMAND ${DARCS_EXE} changes 
> --repodir=${NATIVE_CHICKEN_SOURCE_DIR} > ${NATIVE_CHANGELOG_FILE}
>      )
>
> But I cannot, for the life of me, emit similar code via a FILE(WRITE 
> ...).
>    SOME_HAIRY_PATH(Chicken_SOURCE_DIR REPO)
>      FILE(WRITE ${CMAKE_CURRENT_BINARY_DIR}/try-darcs/CMakeLists.txt "
>      PROJECT(try-darcs)
>      ADD_CUSTOM_TARGET(try
>        COMMAND ${DARCS_EXE} changes --repodir=${REPO} --last=0
>      )
>    ")
>
> It doesn't matter what function I write for SOME_HAIRY_PATH.  Doesn't 
> matter whether I add quotes, a level of escapes, more backslashes, 
> less backslashes, anything, everything, nothing.  The problem is, the 
> act of emitting a file in and of itself consumes quotes.  I should 
> have strings like --repodir=\"E:\devel\src\chicken\" in the above 
> example.  But that doesn't work because the single \ are interpreted 
> as escapes for letters, giving CMake parse errors.   
> --repodir=\"E:\\devel\\src\\chicken\" writes out to the file and 
> doesn't generate parse errors, but the \\ aren't transformed into \, 
> they stay as \\.  So Darcs barfs on them.
>
> VERBATIM didn't seem to solve this.  Did I miss how it was supposed to 
> be used?  Or was FILE(WRITE ...) consumption not considered?
>
>
> Cheers,
> Brandon Van Every
>
>



More information about the CMake mailing list