summaryrefslogtreecommitdiff
path: root/make_to_act_only_on_modified_files_from_prerequisites.html
diff options
context:
space:
mode:
Diffstat (limited to 'make_to_act_only_on_modified_files_from_prerequisites.html')
-rw-r--r--make_to_act_only_on_modified_files_from_prerequisites.html130
1 files changed, 130 insertions, 0 deletions
diff --git a/make_to_act_only_on_modified_files_from_prerequisites.html b/make_to_act_only_on_modified_files_from_prerequisites.html
new file mode 100644
index 0000000..657fc70
--- /dev/null
+++ b/make_to_act_only_on_modified_files_from_prerequisites.html
@@ -0,0 +1,130 @@
+<!doctype html>
+<html lang="en">
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<meta name="author" content="aki">
+<meta name="tags" content="make, makefile, upload, update">
+<meta name="published-on" content="2024-01-23T21:26:54+01:00">
+<link rel="icon" type="image/png" href="favicon.png">
+<link rel="stylesheet" href="style.css">
+
+<title>Make to Act Only on Modified Files From Prerequisites</title>
+
+<header>
+<nav><a href="https://ignore.pl">ignore.pl</a></nav>
+<time>23 January 2024</time>
+<h1>Make to Act Only on Modified Files From Prerequisites</h1>
+</header>
+
+<article>
+<p>Make is very clear on how it tracks changes. There are output files and input files. If input is newer than the
+output, things need building. It may not be <a href="https://gittup.org/tup/make_vs_tup.html">fast</a> and it does not
+lack layers upon layers (e.g., <a href="https://www.gnu.org/software/make/manual/html_node/Archive-Members.html">Archive
+Members</a>), but its essence is rather clear. With:
+<pre>
+%.b: %.a
+ cat $&lt; &gt;$@
+
+hello.b:
+</pre>
+<p>We can read that whenever <i>hello.a</i> is modified a new <i>hello.b</i> will be generated from its content.
+<p>Today let's talk about situation in which we have multiple prerequisite files. A sample rule that permits to
+concatenate multiple <i>.a</i> files can be written in a makefile as follows:
+<pre>
+%.b: %.a
+ cat $^ &gt;$@
+
+hello.b: hi.a hey.a
+</pre>
+<p>We can already make two significant observations. The first point of interest is common and usually, I hope,
+consciously ignored. Usually described as "an implied dependency on <i>hello.a</i>". To be precise, what is implied here
+is not the dependency itself, but the entire recipe - name of the output and one of the inputs. We simply add two more
+dependencies on top of something that is not really visible but matches. If we were to change the name of the output to
+<i>greetings.b</i>, nothing would generate.
+<p>Second thing to note here is that the output does not allow updating. On each run it must regenerate the entire
+<i>hello.b</i>.
+<p>To tweak the first behaviour we can remove prerequisites from the rule:
+<pre>
+%.b:
+ cat $^ &gt;$@
+
+greetings.b: hello.a hi.a hey.a
+</pre>
+<p>This changes rule deduction behaviour a bit since there is no longer a visible input file extension that we can use
+for deduction. The rule is chosen solely with the output pattern. Of course, we also need to specify the previously
+implied input file that matched output name based on the previous rule.
+<p>In order to tweak the rule to allow partial regeneration (or simply: updating), we must change context and use
+different <a href="https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html">automatic variable</a>:
+<code>$?</code>. For now, let's try with just the new variable. The new makefile will look like this:
+<pre>
+%.b:
+ cat $? &gt;$@
+
+greetings.b: hello.a hi.a hey.a
+</pre>
+<p>Context problem is visible only after we perform some subsequent builds:
+<pre>
+$ make
+cat hello.a hi.a hey.a &gt;hello.b
+$ cat hello.b
+hello
+hi
+hey
+$ touch hi.a
+$ make
+cat hi.a &gt;hello.b
+$ cat hello.b
+hi
+</pre>
+<p>First run is just fine, like predicted the problem shows after it and thanks to <b>make</b> printing commands it is
+also nicely visible. Using redirection that appends to the end of a file would simply write another <code>hi</code> at
+the end. The problem is that we are operating on plain text files and simply append to them. The same situation would
+occur with other similar formats (e.g., tarballs).
+<p>The problem is the object does not support updating that's equivalent to the first creation. That's not always the
+case:
+<pre>
+%.b:
+ ar r $@ $?
+
+greetings.b: hello.a hi.a hey.a
+</pre>
+<p><b>ar</b> supports such operation. Consequent runs will replace content of the <i>greetings.b</i> archive as files
+are modified. If a new file is added to the dependencies, it will only append this new files assuming nothing else was
+modified.</p>
+<img src="make_to_act_only_on_modified_files_from_prerequisites-1.png" alt="todo list drawing">
+<p>What if we do not have an output file? Recently I was playing around with a new idea and I used NodeMCU as a platform
+to implement it on. It's small. Its filesystem and set of services is very simple, so usual things like <b>rsync</b>
+were not an option. To reduce the time spent on uploading files I wanted to send only ones that were updated, but they
+were sent to another device, so there was no target file to compare the dates with.
+<p>Usual approach to similar problems is usually just ignoring it and making a phony target instead. Sometimes even with
+a file present we choose to do so, for example installation targets often completely omit output specification.
+<p>Well, if we don't have a file, we simply need to create one:
+<pre>
+upload: .marker
+
+.marker: a.lua b.lua c.lua
+ nodemcu-uploader -p $(PORT) upload $?
+ @touch $@
+
+.PHONY: upload
+</pre>
+<p>Here, a special file <i>.marker</i> is silently created or updated on each upload. Action is performed silently
+thanks to <code>@</code> at the beginning of the command. The idea is to avoid clutter in logs. The additional
+<i>upload</i> target is there to provide a more human recognizable name. Marker filename is not put into a variable to
+reduce the complexity of the example.
+<pre>
+$ make
+nodemcu-uploader -p /dev/ttyUSB0 upload a.lua b.lua c.lua
+$ touch a.lua
+nodecmu-uploader -p /dev/ttyUSB0 upload a.lua
+$ make clean upload
+rm -f .marker
+nodemcu-uploader -p /dev/ttyUSB0 upload a.lua b.lua c.lua
+</pre>
+<p>Of course, state of the files in the device is not tracked and different devices are not recognized. Still, this
+method has a rather good balance between the overall complexity and how often each branch of its logic is used.
+<p>This sounds like a good opportunity to use an archive member target and pretend we put files into the marker file,
+but it will not work as expected, because it will attempt to rebuild (and thus upload) each individual target every
+single time. Shame.
+</article>
+<script src="https://stats.ignore.pl/track.js"></script>