Introduction
Despite all the
- git clone <project url>
- cmake …
- make test
- make package
- Upload generated rpm(s) / deb(s).
This simplicity effectively reduced the dev-op person to just one. We, the developers, write these cmake targets for them.
Naive Way
when I was first working at my current job, I was assigned to their very new (and only) javascript/node product. I’m also their first javascript/node developer, so folks who created the
add_custom_target(installDeps
COMMAND yarn install --frozen-lockfile
WORKSPACE ${CMAKE_CURRENT_SOURCE_DIR}
)
There’s a reason why this section is called Naive Way, this is really inefficient. In the case of our build steps above containing make test
and,make package
it’s gonna be called at least three times. make test
needs to pull its dependencies and make package
will do a make build
and run make build
) to build our rpm/deb files.
In addition to this, make build
is run twice, once make package
Why Targets Re-Run In CMake
So how do you exactly avoid re-running target in
A basic make structure would look like the following:
<target>: <dependencies>
<command to execute>
To compile a hello world Cprogram, it would look like the following:
hello_world.o: hello_world.c
cc -c hello_world.c
Once, it run once and object file target hello_world.o is generated, it doesn’t rerun until:
- target is deleted. In this case, hello_world.o is deleted.
- dependencies are modified. In this case, just hello_world.c
So why is installDeps
installDeps:
yarn install --frozen-lockfile
That’s it! It will always re-run since:
- installDeps file is never generated, and
- there is no dependencies to watch for change.
Now that we know the culprit, let’s find ourselves the solution.
Avoid Re-Running Targets in CMake
To avoid re-running our targets, I proposed the following solution:
Always use files as target
But, yarn install --frozen-lockfile
generates a node_modules directory, not a file. How do we generate a file from yarn install --frozen-lockfile
"postinstall": "tar c node_modules | md5sum > node_modules.md5"
This will generate a file with the md5 hash of the node_modules directory after the yarn install --frozen-lockfile
. Now in our makefile (don’t worry we will convert it back to
node_modules.md5:
yarn install --frozen-lockfile
installDeps: node_modules.md5
This guarantees that it will never re-run installDep
target. There is a new problem now though, we make installDeps
node_modules.md5: yarn.lock
yarn install --frozen-lockfile
installDeps: node_modules.md5
That’s it! This will re-run if package.json/yarn.lock changes or node_modules.md5 is not generated and won’t re-run beyond that. So how does these look in cmake?
add_custom_command(
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/node_modules.md5
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/yarn.lock
COMMAND yarn install --frozen-lockfile
WORKSPACE ${CMAKE_CURRENT_SOURCE_DIR}
)
add_custom_target(installDeps
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/node_modules.md5
)
This pattern can be used on other things. Maybe you have a yarn build
in your make build
and you don’t want to call yarn build
unnecessarily. Similar pattern is used.
Conclusion
We recently released a version and just doing housekeeping things like speeding up the build. I was able to improve our build time from 45 minutes to 13 minutes, and most of the increase is gained from the patterns described above. I hope you reap some sick time by avoiding this redundant builds, not to mention, save your employer some electric bills.