# How to specify a target to be built only once

**URL:** https://discourse.cmake.org/t/how-to-specify-a-target-to-be-built-only-once/2826
**Category:** Usage
**Created:** [February 23, 2021, 11:23pm UTC](https://discourse.cmake.org/t/how-to-specify-a-target-to-be-built-only-once/2826 "2021-02-23T23:23:03Z")
**Posts on this page:** 7
**Page:** 1

<div class="post-metadata">

### Author: ![jwm](https://discourse.cmake.org/user_avatar/discourse.cmake.org/jwm/32/654_2.png) [@jwm](https://discourse.cmake.org/u/jwm)
#### Post date: [February 23, 2021, 11:23pm UTC](https://discourse.cmake.org/t/how-to-specify-a-target-to-be-built-only-once/2826/1 "2021-02-23T23:23:04Z")

</div>

As part of my larger project, I need to retrieve a file from an external SVN repo to be included in the final RPM build step. This repo is kind of slow… about 50% of the time the download times out, but it’s annoying when you are in a heavy edit/compile cycle to have to wait on a useless download.

For now, I have a separate CMakeLists.txt file for this download. I wasn’t sure how to make it a target, so I ended up with this:

```auto
file(DOWNLOAD
  "http://${download_svnhost}/${download_source}"
  ${download_target}
  ${download_credentials}
  ${download_hash}
  INACTIVITY_TIMEOUT 3
  TIMEOUT 5
  STATUS download_status
)

message(INFO " download status: ${download_status}")

### Add the documentation file(s) to the distribution
install(FILES
  ${download_target}
  COMPONENT doc
  DESTINATION doc
  )

```

What I’d like to do is only download this file as a final step prior to building the packages. Can I get there from here?

---

<div class="post-metadata">

### Author: ![ben.boeckel](https://discourse.cmake.org/letter_avatar_proxy/v4/letter/b/ea5d25/32.png) [@ben.boeckel](https://discourse.cmake.org/u/ben.boeckel)
#### Post date: [February 24, 2021, 9:33pm UTC](https://discourse.cmake.org/t/how-to-specify-a-target-to-be-built-only-once/2826/2 "2021-02-24T21:33:56Z")

</div>

You could download the file as part of an `install(SCRIPT)` or `install(CODE)` snippet. I would then skip the download if the file already exists and has a valid checksum.

---

<div class="post-metadata">

### Author: ![jwm](https://discourse.cmake.org/user_avatar/discourse.cmake.org/jwm/32/654_2.png) [@jwm](https://discourse.cmake.org/u/jwm)
#### Post date: [February 24, 2021, 11:21pm UTC](https://discourse.cmake.org/t/how-to-specify-a-target-to-be-built-only-once/2826/3 "2021-02-24T23:21:37Z")

</div>

Thanks. It took a bit of finagling; the documentation for [`install(SCRIPT|CODE)`](https://cmake.org/cmake/help/v3.17/command/install.html?highlight=install#script) is a bit sparse, but I finally got this to more-or-less work

```auto
### Add the documentation file(s) to the distribution
install(CODE "message(INFO \" downloading user's manual (${download_target}) from ${download_url}\")"
  COMPONENT doc
)
install(CODE
  "file(DOWNLOAD
     ${download_url}
     ${download_target}
     ${download_credentials}
     ${download_hash}
     STATUS download_status
  )"
  COMPONENT doc
)
install(CODE "message(INFO \" download complete; status=${download_status}\")"
  COMPONENT doc
)

install(FILES
  ${download_target}
  COMPONENT doc
  DESTINATION doc
)

```

What wasn’t clear to me was that I still needed to have `install(FILES)` after the download (`CODE` apparently doesn’t honor the `DESTINATION` tag). It also wasn’t clear that I couldn’t have multiple commands in the `CODE` block, but I did find that multiple `install(CODE)` blocks worked.

What I stumbled upon digging through the generated **cmake\_install.cmake** file is that _every_ `install` command should have a `COMPONENT` tag (if you use components), otherwise it is attached to the “Unspecified” component. This has the undesirable side-effect of creating a “myprog-Unspecified-vers.arch.rpm” when building RPMs.

The piece I don’t have right is detecting an error in the download. I purposely put in a bogus host and the build completes without incident. Instead of raising an error, is creates a 0-length file and puts it in the RPM. Note that `download_status` is local to the `install(CODE)` command that downloads the file, and so is not viisble in the final message.

---

<div class="post-metadata">

### Author: ![ben.boeckel](https://discourse.cmake.org/letter_avatar_proxy/v4/letter/b/ea5d25/32.png) [@ben.boeckel](https://discourse.cmake.org/u/ben.boeckel)
#### Post date: [February 25, 2021, 2:41pm UTC](https://discourse.cmake.org/t/how-to-specify-a-target-to-be-built-only-once/2826/4 "2021-02-25T14:41:16Z")

</div>

You should be able to do a `message(FATAL_ERROR)` to stop the install process if you detect a problem with the downloaded file.

---

<div class="post-metadata">

### Author: ![jwm](https://discourse.cmake.org/user_avatar/discourse.cmake.org/jwm/32/654_2.png) [@jwm](https://discourse.cmake.org/u/jwm)
#### Post date: [February 25, 2021, 5:45pm UTC](https://discourse.cmake.org/t/how-to-specify-a-target-to-be-built-only-once/2826/5 "2021-02-25T17:45:25Z")

</div>

can you suggest a snippet? As noted, I was unsuccessful in having multiple commands, and the status variable for the download is not visible outside that command.

---

<div class="post-metadata">

### Author: ![ben.boeckel](https://discourse.cmake.org/letter_avatar_proxy/v4/letter/b/ea5d25/32.png) [@ben.boeckel](https://discourse.cmake.org/u/ben.boeckel)
#### Post date: [February 25, 2021, 7:18pm UTC](https://discourse.cmake.org/t/how-to-specify-a-target-to-be-built-only-once/2826/6 "2021-02-25T19:18:15Z")

</div>

Something like:

```cmake
file(DOWNLOAD … STATUS download_res)
if (download_res)
  message(FATAL_ERROR "Failed to download")
endif ()

```

---

<div class="post-metadata">

### Author: ![jwm](https://discourse.cmake.org/user_avatar/discourse.cmake.org/jwm/32/654_2.png) [@jwm](https://discourse.cmake.org/u/jwm)
#### Post date: [February 25, 2021, 7:26pm UTC](https://discourse.cmake.org/t/how-to-specify-a-target-to-be-built-only-once/2826/7 "2021-02-25T19:26:23Z")

</div>

I had trouble with multiple commands inside a CODE so I tried SCRIPT. The problem I ran into was that SCRIPT didn’t inherit variables from the calling context. Or it may have been an order of parsing issue … see below.

After quite a bit of experimentation, I did figure out how to have multiple commands inside a CODE argument. Apparently CMake relies on newlines as statement delimiters, but newlines within strings are allowed. And I also learned the importance of understanding how strings are parsed. One of the key things I learned was that any `"${variable}"` strings have the variable substituted at generation time, not at run time. In order to reference variables set inside my script, I had to escape the `$` to defer substitution until run time.

What I ended up with was this:

```auto
install(CODE "message(INFO \" url=${download_url}\n target=${download_target}\")
file(DOWNLOAD
  ${download_url}
  ${download_target}
  ${download_credentials}
  ${download_hash}
  TIMEOUT 5
  LOG my_log
  STATUS download_status
)
list(GET download_status 0 dl_success)
if (\${dl_success} EQUAL 0)
  message(INFO \" downloaded; status=\${download_status}\")
else()
  message(FATAL_ERROR \" download failed; status=\${download_status}\n---\n\${my_log}---\")
endif()"
  COMPONENT doc
)

install(FILES
  ${download_target}
  COMPONENT doc
  DESTINATION doc
)

```

Once I read @ben.boeckel reply more closely and used the proper argument to `message()`, the packaging failed as desired.

Whew. Thanks for the help.
