Monday, June 15, 2020

Never set 'Copy To Output Directory' to 'Copy always'

What if there was a simple setting that could save you hours?

Visual Studio Properties window showing the "Copy to Output Directory" setting

I thought there was something wrong with the Test Explorer.
I'd make a change in a configuration file that was used by some of my automated tests. Then I'd run all the tests again to see if that broke/fixed anything.
Only, it would take a couple of minutes - before the tests even started! Because the code was being recompiled first.
I would get frustrated.
I would lose my flow.
It was not an enjoyable or productive situation to be in.

And, I thought the culprit was the test harness. Why was it triggering a rebuild of the code? And a rescanning of the built assemblies to look for tests? When nothing in the code had changed?
All that was changed was an external file that was referenced by some of the tests.

It turns out, the problem was due to my lack of understanding of how MSBuild works and how the value of the Copy to Output Directory property is used.

---

For years I've been concerned by the number of times I've heard developers told to set the value of the Copy to Output Directory property (to a value other than "Do not copy") for files that have no need to be copied to the output directory. It seemed like a waste of effort. If the file wasn't needed in the output directory, why waste effort (admittedly--not a lot of effort) copying files around? Ultimately, this didn't seem all that important. I found more important things to focus on and stopped worrying about other people copying files unnecessarily.
Maybe I shouldn't.
Maybe I should have actually learned more about the setting. It may have helped me sooner.

---

I lived with it, taking a while to rebuild and then run my tests. Until I couldn't.
A few weeks ago, I reached my breaking point. I couldn't take it anymore. I would find out why my code was being rebuilt unnecessarily. Even if it took days, I would be better off in the long run to address this delay. If it was something I was doing, I'd fix it. If it was a genuine bug in the tools, I would find out where and raise it accordingly.

You're smart enough to know that I wouldn't have gotten this far if I didn't have a conclusion to this story. I know you've also figured out from the title of this post what the solution was. Anyway...

It took me the best part of the day reading through diagnostic build logs and testing me to the limits of my web-searching abilities but I got there in the end.
The "culprits" were files with the "Copy to Output Directory" set to "Copy always".

Ok, but why?

That's a very good question, and one I asked too.

It turns out that the behavior is due to the way this value is interpreted.
If you know that you always want the latest version of the file in the output directory, under some conditions, it can be quicker (and easier) to copy the file than spend time determining if the files are different.
That's good, but why does it trigger a rebuild? Why not just copy the file built previously?
Because the file built previously may have been modified (or created) by the build. The build process interprets "Copy Always" to mean "copy the file once the build has completed, so it's the most recent version." Because the build process may modify the file, it must rebuild the project to ensure that the file is the latest version. This is easier than trying to work out if the file will be changed by the build process before it is run. (If that's possible at all.)

So, to make that clear:
- "Copy always" means "make sure I always have the most up to date version."
- Because the build process may modify the file, the build process must be run to make sure you have the most up to date version of that file.

As an alternative, "Copy if newer" won't force a rebuild. The argument for why this is the case is (in my opinion) a bit vague, but it avoids those unnecessary builds, and that's what I'm interested in here.


Given how it works, why might you ever want to use "Copy Always"?

I don't know an official reason, but the only one I could come up with is if you needed a file to have a timestamp that corresponds to the build time. That's it.
I can think of no other reason to use the setting "Copy always." Please share if you have another.



Ok. So, what can you do to make sure you're not using the "Copy always" setting?

There are three options.
  1. You could step through every file and check the property. - Very slow. Very tedious, and likely you might miss something.
  2. Open the project file in a text (or XML) editor and check or make changes there as necessary. - Slightly easier but a hassle if you have lots of projects and not something you'll want to do repeatedly.
  3. Install this extension, and it will check every project for you whenever you open one. If it finds a file with the dreaded "Copy always" setting, it will let you know in the output window. It also adds a context menu entry on the project file to fix (change to "copy if newer") all files in that project.


Partial screenshot of entry in context menu


Please install this Visual Studio extension and let me know how you find it or if there are any scenarios it doesn't handle.

.


Was the above useful?
Did you learn anything?
Have you changed a project (or projects) you work on because of this?
Have you thought about how much time this can save you? How much time is spent on each unnecessary build?
How many times each day is your code rebuilt when nothing has changed? How many days will you spend (have you spent) working on that code? How many people are/will working/work on that code-base?
Could it add up to hours?
And how much is an hour worth to you? Or the company you work for?
Isn't that at least worth buying me a coffee to say thank you?
While we can't do it in person, you can do it virtually via https://www.buymeacoffee.com/mrlacey.
I'll be very grateful for whatever you can contribute.
Or, you could sponsor me to continue to create tools like the extension mentioned above by becoming a sponsor on GitHub.

0 comments:

Post a comment