Directory reorg: move code into redo/, generate binaries in bin/.
It's time to start preparing for a version of redo that doesn't work unless we build it first (because it will rely on C modules, and eventually be rewritten in C altogether). To get rolling, remove the old-style symlinks to the main programs, and rename those programs from redo-*.py to redo/cmd_*.py. We'll also move all library functions into the redo/ dir, which is a more python-style naming convention. Previously, install.do was generating wrappers for installing in /usr/bin, which extend sys.path and then import+run the right file. This made "installed" redo work quite differently from running redo inside its source tree. Instead, let's always generate the wrappers in bin/, and not make anything executable except those wrappers. Since we're generating wrappers anyway, let's actually auto-detect the right version of python for the running system; distros can't seem to agree on what to call their python2 binaries (sigh). We'll fill in the right #! shebang lines. Since we're doing that, we can stop using /usr/bin/env, which will a) make things slightly faster, and b) let us use "python -S", which tells python not to load a bunch of extra crap we're not using, thus improving startup times. Annoyingly, we now have to build redo using minimal/do, then run the tests using bin/redo. To make this less annoying, we add a toplevel ./do script that knows the right steps, and a Makefile (whee!) for people who are used to typing 'make' and 'make test' and 'make clean'.
This commit is contained in:
parent
5bc7c861b6
commit
f6fe00db5c
140 changed files with 256 additions and 99 deletions
5
docs/.gitignore
vendored
Normal file
5
docs/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
*.1
|
||||
/md-to-man
|
||||
*.html
|
||||
*.man.md
|
||||
*.list
|
||||
67
docs/Contributing.md
Normal file
67
docs/Contributing.md
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
# License
|
||||
|
||||
My version of redo was written without ever seeing redo code by Bernstein or
|
||||
Grosskurth, so I own the entire copyright. It's distributed under the terms
|
||||
of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0),
|
||||
which you can find in the file called
|
||||
[LICENSE](https://github.com/apenwarr/redo/tree/master/LICENSE). (Previous
|
||||
versions of redo were distributed under the GNU LGPL, but this [changed in
|
||||
2018](https://groups.google.com/forum/#!topic/redo-list/wLMZMxtn4wo).)
|
||||
|
||||
minimal/do is in the public domain so that it's as easy as possible
|
||||
to include inside your own projects, to help end users who don't
|
||||
have a copy of redo.
|
||||
|
||||
|
||||
# How can I help?
|
||||
|
||||
Nowadays, redo is good enough for real production use, and some people
|
||||
are using it for real work. That said, it has
|
||||
not reached version 1.0 and there are surely still bugs.
|
||||
|
||||
If you run into a problem, it's really helpful if you report it to the
|
||||
mailing list below (with or without subscribing first). We really want to
|
||||
know if redo is acting weird for you. Even if the problem turns out to be
|
||||
operator error, we can use that information to try to improve this
|
||||
documentation.
|
||||
|
||||
Small feature additions are also welcome, but you might want to ask on the
|
||||
mailing list before you start working on it. The code is still evolving and
|
||||
might not be the same by the time you submit your pull request.
|
||||
|
||||
The best things you can do for redo are:
|
||||
|
||||
- Convert your projects to using it. Without users, no project is
|
||||
successful.
|
||||
|
||||
- Build new infrastructure around redo, especially things to make it easier
|
||||
for people to get started. For example, an automake-like tool that filled
|
||||
in default redo build rules for common program types would probably be
|
||||
very popular.
|
||||
|
||||
- Convince your favourite OS distro to build and include (up to date!) redo
|
||||
packages.
|
||||
|
||||
- Help proofreading this documentation, and send patches or pull requests
|
||||
with improvements.
|
||||
|
||||
- Join the mailing list (below) to discuss design changes or bugs.
|
||||
|
||||
- Tell your friends!
|
||||
|
||||
|
||||
# Mailing list
|
||||
|
||||
You should join the `redo-list@googlegroups.com` mailing list.
|
||||
|
||||
You can find the mailing list archives here:
|
||||
<http://groups.google.com/group/redo-list>
|
||||
|
||||
It might not look like it, but you can subscribe without having a
|
||||
Google Account. Just send a message to
|
||||
`redo-list+subscribe@googlegroups.com` (note the plus sign).
|
||||
|
||||
It's okay to send a message directly to the mailing list
|
||||
without subscribing first. If you reply to someone who writes to the
|
||||
list, please leave them in the cc: list, since if they
|
||||
haven't subscribed, they won't get your reply otherwise.
|
||||
143
docs/FAQBasics.md
Normal file
143
docs/FAQBasics.md
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
# Help! redo rebuilds every time, even if dependencies are clean!
|
||||
|
||||
Many people are confused by this at first. The `redo` command *always*
|
||||
rebuilds the target you requested. If you want to only conditionally
|
||||
rebuild the target, use `redo-ifchange`.
|
||||
|
||||
The same rule applies inside .do scripts. If a .do script calls `redo dep`,
|
||||
then it will always redo the target called dep. If it called `redo-ifchange
|
||||
dep`, it will only redo dep if its dependencies have changed.
|
||||
|
||||
Typically, you want your toplevel `all.do` script to use `redo-ifchange`.
|
||||
But a `clean.do` script probably should use `redo`, because you want it to
|
||||
always clean up your extra files. A `test.do` probably also uses `redo` for
|
||||
its testing sub-tasks, because you might want to run your tests repeatedly.
|
||||
|
||||
For rationale, see [Why does 'redo target' redo even unchanged
|
||||
targets?](../FAQSemantics/#why-does-redo-target-redo-even-unchanged-targets)
|
||||
|
||||
|
||||
# I'm using redo-ifchange. How does redo decide my target is clean/dirty?
|
||||
|
||||
You can see (recursively) which dependencies checked by redo, and whether
|
||||
they are clean or dirty, by using `redo -d` (which is short for
|
||||
"dependencies" or "debug", whichever you prefer).
|
||||
|
||||
|
||||
# Does redo make cross-platform or multi-platform builds easy?
|
||||
|
||||
A lot of build systems that try to replace make do it by trying to provide a
|
||||
lot of predefined rules. For example, one build system I know includes
|
||||
default rules that can build C++ programs on Visual C++ or gcc,
|
||||
cross-compiled or not cross-compiled, and so on. Other build systems are
|
||||
specific to ruby programs, or python programs, or Java or .Net programs.
|
||||
|
||||
redo isn't like those systems; it's more like make. It doesn't know
|
||||
anything about your system or the language your program is written in.
|
||||
|
||||
The good news is: redo will work with *any* programming language with about
|
||||
equal difficulty. The bad news is: you might have to fill in more details
|
||||
than you would if you just use ANT to compile a Java program.
|
||||
|
||||
So the short version is: cross-platform builds are about equally easy in
|
||||
make and redo. It's not any easier, but it's not any harder.
|
||||
|
||||
It would be possible to make an automake-like or cmake-like tool that
|
||||
generates .do files for your project, just like automake generates
|
||||
Makefiles. But that's beyond the scope of redo itself.
|
||||
|
||||
|
||||
# Can I set my dircolors to highlight .do files in ls output?
|
||||
|
||||
Yes! At first, having a bunch of .do files in each
|
||||
directory feels like a bit of a nuisance, but once you get
|
||||
used to it, it's actually pretty convenient; a simple 'ls'
|
||||
will show you which things you might want to redo in any
|
||||
given directory.
|
||||
|
||||
Here's a chunk of my .dircolors.conf:
|
||||
|
||||
.do 00;35
|
||||
*Makefile 00;35
|
||||
.o 00;30;1
|
||||
.pyc 00;30;1
|
||||
*~ 00;30;1
|
||||
.tmp 00;30;1
|
||||
|
||||
To activate it, you can add a line like this to your .bashrc:
|
||||
|
||||
eval `dircolors $HOME/.dircolors.conf`
|
||||
|
||||
|
||||
# Do end users have to have redo installed in order to build my project?
|
||||
|
||||
No. We include a very short and simple shell script
|
||||
called `do` in the `minimal/` subdirectory of the redo project.
|
||||
`minimal/do` is like
|
||||
`redo` (and it works with the same `*.do` scripts), except it doesn't
|
||||
understand dependencies; it just always rebuilds everything from the top.
|
||||
|
||||
You can include `do` with your program so that non-users of redo can
|
||||
still build your program. Someone who wants to hack on your program will
|
||||
probably go crazy unless they install a copy of `redo` though.
|
||||
|
||||
Actually, `redo` itself isn't so big, so for large projects where it
|
||||
matters, you could just include it with your project.
|
||||
|
||||
|
||||
# Recursive make is considered harmful. Isn't redo even *more* recursive?
|
||||
|
||||
You probably mean [this 1997 paper](http://miller.emu.id.au/pmiller/books/rmch/)
|
||||
by Peter Miller.
|
||||
|
||||
Yes, redo is recursive, in the sense that every target is built by its own
|
||||
`.do` file, and every `.do` file is a shell script being run recursively
|
||||
from other shell scripts, which might call back into `redo`. In fact, it's
|
||||
even more recursive than recursive make. There is no
|
||||
non-recursive way to use redo.
|
||||
|
||||
However, the reason recursive make is considered harmful is that each
|
||||
instance of make has no access to the dependency information seen by the
|
||||
other instances. Each one starts from its own Makefile, which only has a
|
||||
partial picture of what's going on; moreover, each one has to
|
||||
stat() a lot of the same files over again, leading to slowness. That's
|
||||
the thesis of the "considered harmful" paper.
|
||||
|
||||
It turns out that [non-recursive make should also be considered harmful](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/03/hadrian.pdf).
|
||||
The problem is Makefiles aren't
|
||||
very "hygienic" or "modular"; if you're not running make recursively, then
|
||||
your one copy of make has to know *everything* about *everything* in your
|
||||
entire project. Every variable in make is global, so every variable defined
|
||||
in *any* of your Makefiles is visible in *all* of your Makefiles. Every
|
||||
little private function or macro is visible everywhere. In a huge project
|
||||
made up of multiple projects from multiple vendors, that's just not okay.
|
||||
Plus, if all your Makefiles are tangled together, make has
|
||||
to read and parse the entire mess even to build the
|
||||
smallest, simplest target file, making it slow.
|
||||
|
||||
`redo` deftly dodges both the problems of recursive make
|
||||
and the problems of non-recursive make. First of all,
|
||||
dependency information is shared through a global persistent `.redo`
|
||||
database, which is accessed by all your `redo` instances at once.
|
||||
Dependencies created or checked by one instance can be immediately used by
|
||||
another instance. And there's locking to prevent two instances from
|
||||
building the same target at the same time. So you get all the "global
|
||||
dependency" knowledge of non-recursive make. And it's a
|
||||
binary file, so you can just grab the dependency
|
||||
information you need right now, rather than going through
|
||||
everything linearly.
|
||||
|
||||
Also, every `.do` script is entirely hygienic and traceable; `redo`
|
||||
discourages the use of global environment variables, suggesting that you put
|
||||
settings into files (which can have timestamps and dependencies) instead.
|
||||
So you also get all the hygiene and modularity advantages of recursive make.
|
||||
|
||||
By the way, you can trace any `redo` build process just by reading the `.do`
|
||||
scripts from top to bottom. Makefiles are actually a collection of "rules"
|
||||
whose order of execution is unclear; any rule might run at any time. In a
|
||||
non-recursive Makefile setup with a bunch of included files, you end up with
|
||||
lots and lots of rules that can all be executed in a random order; tracing
|
||||
becomes impossible. Recursive make tries to compensate for this by breaking
|
||||
the rules into subsections, but that ends up with all the "considered harmful"
|
||||
paper's complaints. `redo` runs your scripts from top to bottom in a
|
||||
nice tree, so it's traceable no matter how many layers you have.
|
||||
371
docs/FAQImpl.md
Normal file
371
docs/FAQImpl.md
Normal file
|
|
@ -0,0 +1,371 @@
|
|||
# Does redo run on Windows?
|
||||
|
||||
redo works fine in [Windows Services for Linux (WSL)](https://docs.microsoft.com/en-us/windows/wsl/initialize-distro)
|
||||
on Windows 10. You might consider that to be "real" Windows, or not. If
|
||||
you use it, it runs more or less like it does on Linux. WSL is considerably
|
||||
slower than native Linux, so there is definitely room for speed improvements.
|
||||
|
||||
If I were going to port redo to Windows in a "native" way,
|
||||
I might grab the source code to a posix shell (like the
|
||||
one in MSYS) and link it directly into redo.
|
||||
|
||||
One interesting project that has appeared recently is
|
||||
busybox-w32 (https://github.com/pclouds/busybox-w32). It's
|
||||
a port of busybox to win32 that includes a mostly POSIX
|
||||
shell (ash) and a bunch of standard Unix utilities. This
|
||||
might be enough to get your redo scripts working on a win32
|
||||
platform without having to install a bunch of stuff. But
|
||||
all of this needs more experimentation.
|
||||
|
||||
|
||||
# Can a *.do file itself be generated as part of the build process?
|
||||
|
||||
Yes and no. Kind of. Redo doesn't stop you from doing this, and it will
|
||||
use a .do file if you generate one. However, you have to do the steps in
|
||||
the right order if you want this to work. For example, this should work
|
||||
fine:
|
||||
|
||||
redo-ifchange default.o.do
|
||||
redo-ifchange foo.o
|
||||
|
||||
You've told redo to generate `default.o.do` (presumably using a
|
||||
script like default.do.do), and *then* you've told redo to generate `foo.o`.
|
||||
When it considers how to build `foo.o`, it will look for a .do file, and
|
||||
thus find `default.o.do`, so it'll run it. Great.
|
||||
|
||||
Some people would like us to go a step further, and *automatically* look for
|
||||
rules that will help produce default.o.do. That is, you might want to just
|
||||
write this:
|
||||
|
||||
redo-ifchange foo.o bar.o
|
||||
|
||||
and then expect that, implicitly, redo will know it needs to look for
|
||||
`default.o.do`, and if `default.o.do` doesn't exist yet, it should look for
|
||||
`default.do.do`, and so on.
|
||||
|
||||
The problem with this idea is... where does it end? If there's no
|
||||
`default.o.do`, so we look for a `default.do.do`, what if that doesn't exist
|
||||
either? Perhaps there's a `default.do.do.do` for generating `default.do.do`
|
||||
files? And so on. You'd have to draw an arbitrary line somewhere.
|
||||
|
||||
Anyway, unlike make, redo does *not* implicitly generate files.
|
||||
It only generates a given file if you redo-ifchange that file
|
||||
first, from one of your other .do scripts. This gets avoids magical
|
||||
behaviour, but makes it quite tricky to auto-generate .do files. See the
|
||||
next question for a workaround.
|
||||
|
||||
|
||||
# What can I do instead of auto-generating *.do files?
|
||||
|
||||
When people ask about auto-generating .do files, they usually mean one of
|
||||
two things:
|
||||
|
||||
1. They want to create a new directory and auto-populate it with .do files
|
||||
copied from somewhere.
|
||||
|
||||
- This can be solved in various ways. For example, you might make a
|
||||
trivial toplevel `default.do` file in the new directory; this will
|
||||
match all possible targets. You can then use the sh "source" operator
|
||||
(.) and `redo-whichdo` to pull the "real" rules from elsewhere. An
|
||||
example of [.do file delegation can be found in the wvstreams
|
||||
project](https://github.com/apenwarr/wvstreams/blob/master/delegate.od).
|
||||
|
||||
2. They want to generate, eg. `default.o.do` based on auto-detected compiler
|
||||
settings, in order to support different brands of compilers, or different
|
||||
architectures, etc.
|
||||
|
||||
- The trick here is not to generate `default.o.do`, but rather to
|
||||
generate another script instead. For example, you could have a
|
||||
`compile.do` that generates a script called `compile`. `default.o.do`
|
||||
would simply be hard coded to something like `./compile $2 $3`. The
|
||||
wvstreams project also has [an example of
|
||||
compile.do](https://github.com/apenwarr/wvstreams/blob/master/compile.do).
|
||||
|
||||
- The advantage of separating out into separate default.o.do and compile
|
||||
scripts is that you can put all your system-dependent conditionals
|
||||
into compile.do, so they run only once and choose the "right" compile
|
||||
command. Then, for each file, you can simply run that command. If
|
||||
This kind of micro-optimization doesn't appeal to you, then there's no
|
||||
shame in just putting all the logic directly into `default.o.do`.
|
||||
|
||||
|
||||
# How does redo store dependencies?
|
||||
|
||||
At the toplevel of your project, redo creates a directory
|
||||
named `.redo`. That directory contains a sqlite3 database
|
||||
with dependency information.
|
||||
|
||||
The format of the `.redo` directory is undocumented because
|
||||
it may change at any time. It will likely turn out that we
|
||||
can do something simpler than sqlite3. If you really need to make a
|
||||
tool that pokes around in there, please ask on the mailing
|
||||
list if we can standardize something for you.
|
||||
|
||||
Unfortunately, the design of having a *single* .redo directory at the
|
||||
toplevel of a project has proven problematic: what exactly is the "top
|
||||
level" of a "project"? If your project is a subdirectory of another project
|
||||
that then switches to redo, should the .redo directory move up a level in
|
||||
the hierarchy when that happens? And so on. Eventually, we will
|
||||
probably migrate to a system where there is one .redo directory per target
|
||||
directory. This avoids all kinds of problems with symlinks, directory
|
||||
renames nested projects, and so on.
|
||||
|
||||
# Isn't using sqlite3 overkill? And un-djb-ish?
|
||||
|
||||
Well, yes. Sort of. I think people underestimate how
|
||||
"lite" sqlite really is:
|
||||
|
||||
root root 573376 2010-10-20 09:55 /usr/lib/libsqlite3.so.0.8.6
|
||||
|
||||
573k for a *complete* and *very fast* and *transactional*
|
||||
SQL database. For comparison, libdb is:
|
||||
|
||||
root root 1256548 2008-09-13 03:23 /usr/lib/libdb-4.6.so
|
||||
|
||||
...more than twice as big, and it doesn't even have an SQL parser in
|
||||
it! Or if you want to be really horrified:
|
||||
|
||||
root root 1995612 2009-02-03 13:54 /usr/lib/libmysqlclient.so.15.0.0
|
||||
|
||||
The mysql *client* library is two megs, and it doesn't even
|
||||
have a database in it! People who think SQL
|
||||
databases are automatically bloated and gross have not yet
|
||||
actually experienced the joys of sqlite. SQL has a
|
||||
well-deserved bad reputation, but sqlite is another story
|
||||
entirely. It's excellent, and much simpler and better
|
||||
written than you'd expect.
|
||||
|
||||
Still, it's not very "djbish" to use a
|
||||
general-purpose database, especially one that has a *SQL
|
||||
parser* in it. (One of the great things about redo's
|
||||
design is that it doesn't ever need to parse anything, so
|
||||
embedding a whole SQL parser is a bit embarrassing.)
|
||||
|
||||
I'm pretty sure djb never would have done it that way.
|
||||
However, I don't think we can reach the performance we want
|
||||
with dependency/build/lock information stored in plain text
|
||||
files; among other things, that results in too much
|
||||
fstat/open activity, which is slow in general, and even
|
||||
slower if you want to run on Windows. That leads us to a
|
||||
binary database. And example of the kind of structure we need is the [one
|
||||
used by ninja](https://github.com/ninja-build/ninja/blob/master/src/deps_log.h#L29)
|
||||
which is very simple, fast, and efficient.
|
||||
|
||||
Most of the state database stuff has been isolated in
|
||||
state.py. If you're feeling brave, you can try to
|
||||
implement your own better state database, with or without
|
||||
sqlite.
|
||||
|
||||
|
||||
# What hash algorithm does redo-stamp use?
|
||||
|
||||
It's intentionally undocumented because you shouldn't need
|
||||
to care and it might change at any time. But trust me,
|
||||
it's not the slow part of your build, and you'll never
|
||||
accidentally get a hash collision.
|
||||
|
||||
|
||||
# Why not *always* use checksum-based dependencies instead of timestamps?
|
||||
|
||||
Some build systems keep a checksum of target files and rebuild dependents
|
||||
only when the target changes. This is appealing in some cases; for example,
|
||||
with ./configure generating config.h, it could just go ahead and generate
|
||||
config.h; the build system would be smart enough to rebuild or not rebuild
|
||||
dependencies automatically. This keeps build scripts simple and gets rid of
|
||||
the need for people to re-implement file comparison over and over in every
|
||||
project or for multiple files in the same project.
|
||||
|
||||
There are disadvantages to using checksums for everything
|
||||
automatically, however:
|
||||
|
||||
- Building stuff unnecessarily is *much* less dangerous
|
||||
than not building stuff that should be built. Checksums
|
||||
aren't perfect (think of zero-byte output files); using
|
||||
checksums will cause more builds to be skipped by
|
||||
default, which is very dangerous.
|
||||
|
||||
- It makes it hard to *force* things to rebuild when you
|
||||
know you absolutely want that. (With timestamps, you can
|
||||
just `touch filename` to rebuild everything that depends
|
||||
on `filename`.)
|
||||
|
||||
- Targets that are just used for aggregation (ie. they
|
||||
don't produce any output of their own) would always have
|
||||
the same checksum - the checksum of a zero-byte file -
|
||||
which causes confusing results.
|
||||
|
||||
- Calculating checksums for every output file adds time to
|
||||
the build, even if you don't need that feature.
|
||||
|
||||
- Building stuff unnecessarily and then stamping it is
|
||||
much slower than just not building it in the first place,
|
||||
so for *almost* every use of redo-stamp, it's not the
|
||||
right solution anyway.
|
||||
|
||||
- To steal a line from the Zen of Python: explicit is
|
||||
better than implicit. Making people think about when
|
||||
they're using the stamp feature - knowing that it's slow
|
||||
and a little annoying to do - will help people design
|
||||
better build scripts that depend on this feature as
|
||||
little as possible.
|
||||
|
||||
- djb's (as yet unreleased) version of redo doesn't
|
||||
implement checksums, so doing that would produce an
|
||||
incompatible implementation. With redo-stamp and
|
||||
redo-always being separate programs, you can simply
|
||||
choose not to use them if you want to keep maximum
|
||||
compatibility for the future.
|
||||
|
||||
- Bonus: the redo-stamp algorithm is interchangeable. You
|
||||
don't have to stamp the target file or the source files
|
||||
or anything in particular; you can stamp any data you
|
||||
want, including the output of `ls` or the content of a
|
||||
web page. We could never have made things like that
|
||||
implicit anyway, so some form of explicit redo-stamp
|
||||
would always have been needed, and then we'd have to
|
||||
explain when to use the explicit one and when to use the
|
||||
implicit one.
|
||||
|
||||
Thus, we made the decision to only use checksums for
|
||||
targets that explicitly call `redo-stamp` (see previous
|
||||
question).
|
||||
|
||||
I suggest actually trying it out to see how it feels for
|
||||
you. For myself, before there was redo-stamp and
|
||||
redo-always, a few types of problems (in particular,
|
||||
depending on a list of which files exist and which don't)
|
||||
were really annoying, and I definitely felt it. Adding
|
||||
redo-stamp and redo-always work the way they do made the
|
||||
pain disappear, so I stopped changing things.
|
||||
|
||||
A longer and even more detailed explanation of timestamp vs checksum-based
|
||||
build dependencies can be found in
|
||||
[mtime comparison considered harmful](https://apenwarr.ca/log/20181113).
|
||||
|
||||
|
||||
# Why doesn't redo by default print the commands as they are run?
|
||||
|
||||
make prints the commands it runs as it runs them. redo doesn't, although
|
||||
you can get this behaviour with `redo -v` or `redo -x`.
|
||||
(The difference between -v and -x is the same as it is in
|
||||
sh... because we simply forward those options onward to sh
|
||||
as it runs your .do script.)
|
||||
|
||||
The main reason we don't do this by default is that the commands get
|
||||
pretty long
|
||||
winded (a compiler command line might be multiple lines of repeated
|
||||
gibberish) and, on large projects, it's hard to actually see the progress of
|
||||
the overall build. Thus, make users often work hard to have make hide the
|
||||
command output in order to make the log "more readable."
|
||||
|
||||
The reduced output is a pain with make, however, because if there's ever a
|
||||
problem, you're left wondering exactly what commands were run at what time,
|
||||
and you often have to go editing the Makefile in order to figure it out.
|
||||
|
||||
With redo, it's much less of a problem. By default, redo produces output
|
||||
that looks like this:
|
||||
|
||||
$ redo t
|
||||
redo t/all
|
||||
redo t/hello
|
||||
redo t/LD
|
||||
redo t/hello.o
|
||||
redo t/CC
|
||||
redo t/yellow
|
||||
redo t/yellow.o
|
||||
redo t/bellow
|
||||
redo t/c
|
||||
redo t/c.c
|
||||
redo t/c.c.c
|
||||
redo t/c.c.c.b
|
||||
redo t/c.c.c.b.b
|
||||
redo t/d
|
||||
|
||||
The indentation indicates the level of recursion (deeper levels are
|
||||
dependencies of earlier levels). The repeated word "redo" down the left
|
||||
column looks strange, but it's there for a reason, and the reason is this:
|
||||
you can cut-and-paste a line from the build script and rerun it directly.
|
||||
|
||||
$ redo t/c
|
||||
redo t/c
|
||||
redo t/c.c
|
||||
redo t/c.c.c
|
||||
redo t/c.c.c.b
|
||||
redo t/c.c.c.b.b
|
||||
|
||||
So if you ever want to debug what happened at a particular step, you can
|
||||
choose to run only that step in verbose mode:
|
||||
|
||||
$ redo t/c.c.c.b.b -x
|
||||
redo t/c.c.c.b.b
|
||||
* sh -ex default.b.do c.c.c.b .b c.c.c.b.b.redo2.tmp
|
||||
+ redo-ifchange c.c.c.b.b.a
|
||||
+ echo a-to-b
|
||||
+ cat c.c.c.b.b.a
|
||||
+ ./sleep 1.1
|
||||
redo t/c.c.c.b.b (done)
|
||||
|
||||
If you're using an autobuilder or something that logs build results for
|
||||
future examination, you should probably set it to always run redo with
|
||||
the -x option.
|
||||
|
||||
|
||||
# How fast is redo compared to make?
|
||||
|
||||
FIXME:
|
||||
The current version of redo is written in python and has not been optimized.
|
||||
So right now, it's usually a bit slower. Not too embarrassingly slower,
|
||||
though, and the slowness mostly only strikes when you're
|
||||
building a project from scratch.
|
||||
|
||||
For incrementally building only the changed parts of the project, redo can
|
||||
be much faster than make, because it can check all the dependencies up
|
||||
front and doesn't need to repeatedly parse and re-parse the Makefile (as
|
||||
recursive make needs to do).
|
||||
|
||||
redo's sqlite3-based dependency database is very fast (and
|
||||
it would be even faster if we rewrite redo in C instead of
|
||||
python). Better still, it would be possible to write an
|
||||
inotify daemon that can update the dependency database in
|
||||
real time; if you're running the daemon, you can run 'redo'
|
||||
from the toplevel and if your build is clean, it could return
|
||||
instantly, no matter how many dependencies you have.
|
||||
|
||||
On my machine, redo can currently check about 10,000
|
||||
dependencies per second. As an example, a program that
|
||||
depends on every single .c or .h file in the Linux kernel
|
||||
2.6.36 repo (about 36000 files) can be checked in about 4
|
||||
seconds.
|
||||
|
||||
Rewritten in C, dependency checking would probably go about
|
||||
10 times faster still.
|
||||
|
||||
This probably isn't too hard; the design of redo is so simple that
|
||||
it should be easy to write in any language. It's just
|
||||
*even easier* in python, which was good for writing the
|
||||
prototype and debugging the parallelism and locking rules.
|
||||
|
||||
Most of the slowness at the moment is because redo-ifchange
|
||||
(and also sh itself) need to be fork'd and exec'd over and
|
||||
over during the build process.
|
||||
|
||||
As a point of reference, on my computer, I can fork-exec
|
||||
redo-ifchange.py about 87 times per second; an empty python
|
||||
program, about 100 times per second; an empty C program,
|
||||
about 1000 times per second; an empty make, about 300 times
|
||||
per second. So if I could compile 87 files per second with
|
||||
gcc, which I can't because gcc is slower than that, then
|
||||
python overhead would be 50%. Since gcc is slower than
|
||||
that, python overhead is generally much less - more like
|
||||
10%.
|
||||
|
||||
Also, if you're using redo -j on a multicore machine, all
|
||||
the python forking happens in parallel with everything
|
||||
else, so that's 87 per second per core. Nevertheless,
|
||||
that's still slower than make and should be fixed.
|
||||
|
||||
(On the other hand, all this measurement is confounded
|
||||
because redo's more fine-grained dependencies mean you can
|
||||
have more parallelism. So if you have a lot of CPU cores, redo
|
||||
might build *faster* than make just because it makes better
|
||||
use of them.)
|
||||
55
docs/FAQInterop.md
Normal file
55
docs/FAQInterop.md
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# Is redo compatible with autoconf?
|
||||
|
||||
Yes. You don't have to do anything special, other than the above note about
|
||||
declaring dependencies on config.h, which is no worse than what you would
|
||||
have to do with make.
|
||||
|
||||
|
||||
# Is redo compatible with automake?
|
||||
|
||||
Hells no. You can thank me later. But see next question.
|
||||
|
||||
|
||||
# Is redo compatible with make?
|
||||
|
||||
Yes. If you have an existing Makefile (for example, in one of your
|
||||
subprojects), you can just call make from a .do script to build that
|
||||
subproject.
|
||||
|
||||
In a file called myproject.stamp.do:
|
||||
|
||||
redo-ifchange $(find myproject -name '*.[ch]')
|
||||
make -C myproject all
|
||||
|
||||
So, to amend our answer to the previous question, you *can* use
|
||||
automake-generated Makefiles as part of your redo-based project.
|
||||
|
||||
|
||||
# Is redo -j compatible with make -j?
|
||||
|
||||
Yes! redo implements the same jobserver protocol as GNU make, which means
|
||||
that redo running under make -j, or make running under redo -j, will do the
|
||||
right thing. Thus, it's safe to mix-and-match redo and make in a recursive
|
||||
build system.
|
||||
|
||||
Just make sure you declare your dependencies correctly;
|
||||
redo won't know all the specific dependencies included in
|
||||
your Makefile, and make won't know your redo dependencies,
|
||||
of course.
|
||||
|
||||
One way of cheating is to just have your make.do script
|
||||
depend on *all* the source files of a subproject, like
|
||||
this:
|
||||
|
||||
make -C subproject all
|
||||
find subproject -name '*.[ch]' | xargs redo-ifchange
|
||||
|
||||
Now if any of the .c or .h files in subproject are changed,
|
||||
your make.do will run, which calls into the subproject to
|
||||
rebuild anything that might be needed. Worst case, if the
|
||||
dependencies are too generous, we end up calling 'make all'
|
||||
more often than necessary. But 'make all' probably runs
|
||||
pretty fast when there's nothing to do, so that's not so
|
||||
bad.
|
||||
|
||||
|
||||
158
docs/FAQParallel.md
Normal file
158
docs/FAQParallel.md
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
# Parallelism if more than one target depends on the same subdir
|
||||
|
||||
Recursive make is especially painful when it comes to
|
||||
parallelism. Take a look at this Makefile fragment:
|
||||
|
||||
all: fred bob
|
||||
subproj:
|
||||
touch $@.new
|
||||
sleep 1
|
||||
mv $@.new $@
|
||||
fred:
|
||||
$(MAKE) subproj
|
||||
touch $@
|
||||
bob:
|
||||
$(MAKE) subproj
|
||||
touch $@
|
||||
|
||||
If we run it serially, it all looks good:
|
||||
|
||||
$ rm -f subproj fred bob; make --no-print-directory
|
||||
make subproj
|
||||
touch subproj.new
|
||||
sleep 1
|
||||
mv subproj.new subproj
|
||||
touch fred
|
||||
make subproj
|
||||
make[1]: 'subproj' is up to date.
|
||||
touch bob
|
||||
|
||||
But if we run it in parallel, life sucks:
|
||||
|
||||
$ rm -f subproj fred bob; make -j2 --no-print-directory
|
||||
make subproj
|
||||
make subproj
|
||||
touch subproj.new
|
||||
touch subproj.new
|
||||
sleep 1
|
||||
sleep 1
|
||||
mv subproj.new subproj
|
||||
mv subproj.new subproj
|
||||
mv: cannot stat 'ubproj.new': No such file or directory
|
||||
touch fred
|
||||
make[1]: *** [subproj] Error 1
|
||||
make: *** [bob] Error 2
|
||||
|
||||
What happened? The sub-make that runs `subproj` ended up
|
||||
getting twice at once, because both fred and bob need to
|
||||
build it.
|
||||
|
||||
If fred and bob had put in a *dependency* on subproj, then
|
||||
GNU make would be smart enough to only build one of them at
|
||||
a time; it can do ordering inside a single make process.
|
||||
So this example is a bit contrived. But imagine that fred
|
||||
and bob are two separate applications being built from the
|
||||
same toplevel Makefile, and they both depend on the library
|
||||
in subproj. You'd run into this problem if you use
|
||||
recursive make.
|
||||
|
||||
Of course, you might try to solve this by using
|
||||
*nonrecursive* make, but that's really hard. What if
|
||||
subproj is a library from some other vendor? Will you
|
||||
modify all their makefiles to fit into your nonrecursive
|
||||
makefile scheme? Probably not.
|
||||
|
||||
Another common workaround is to have the toplevel Makefile
|
||||
build subproj, then fred and bob. This works, but if you
|
||||
don't run the toplevel Makefile and want to go straight
|
||||
to work in the fred project, building fred won't actually
|
||||
build subproj first, and you'll get errors.
|
||||
|
||||
redo solves all these problems. It maintains global locks
|
||||
across all its instances, so you're guaranteed that no two
|
||||
instances will try to build subproj at the same time. And
|
||||
this works even if subproj is a make-based project; you
|
||||
just need a simple subproj.do that runs `make subproj`.
|
||||
|
||||
|
||||
# Dependency problems that only show up during parallel builds
|
||||
|
||||
One annoying thing about parallel builds is... they do more
|
||||
things in parallel. A very common problem in make is to
|
||||
have a Makefile rule that looks like this:
|
||||
|
||||
all: a b c
|
||||
|
||||
When you `make all`, it first builds a, then b, then c.
|
||||
What if c depends on b? Well, it doesn't matter when
|
||||
you're building in serial. But with -j3, you end up
|
||||
building a, b, and c at the same time, and the build for c
|
||||
crashes. You *should* have said:
|
||||
|
||||
all: a b c
|
||||
c: b
|
||||
b: a
|
||||
|
||||
and that would have fixed it. But you forgot, and you
|
||||
don't find out until you build with exactly the wrong -j
|
||||
option.
|
||||
|
||||
This mistake is easy to make in redo too. But it does have
|
||||
a tool that helps you debug it: the --shuffle option.
|
||||
--shuffle takes the dependencies of each target, and builds
|
||||
them in a random order. So you can get parallel-like
|
||||
results without actually building in parallel.
|
||||
|
||||
|
||||
# What about distributed builds?
|
||||
|
||||
FIXME:
|
||||
So far, nobody has tried redo in a distributed build environment. It surely
|
||||
works with distcc, since that's just a distributed compiler. But there are
|
||||
other systems that distribute more of the build process to other machines.
|
||||
|
||||
The most interesting method I've heard of was explained (in public, this is
|
||||
not proprietary information) by someone from Google. Apparently, the
|
||||
Android team uses a tool that mounts your entire local filesystem on a
|
||||
remote machine using FUSE and chroots into that directory. Then you replace
|
||||
the $SHELL variable in your copy of make with one that runs this tool.
|
||||
Because the remote filesystem is identical to yours, the build will
|
||||
certainly complete successfully. After the $SHELL program exits, the changed
|
||||
files are sent back to your local machine. Cleverly, the files on the
|
||||
remote server are cached based on their checksums, so files only need to be
|
||||
re-sent if they have changed since last time. This dramatically reduces
|
||||
bandwidth usage compared to, say, distcc (which mostly just re-sends the
|
||||
same preparsed headers over and over again).
|
||||
|
||||
At the time, he promised to open source this tool eventually. It would be
|
||||
pretty fun to play with it.
|
||||
|
||||
The problem:
|
||||
|
||||
This idea won't work as easily with redo as it did with
|
||||
make. With make, a separate copy of $SHELL is launched for
|
||||
each step of the build (and gets migrated to the remote
|
||||
machine), but make runs only on your local machine, so it
|
||||
can control parallelism and avoid building the same target
|
||||
from multiple machines, and so on. The key to the above
|
||||
distribution mechanism is it can send files to the remote
|
||||
machine at the beginning of the $SHELL, and send them back
|
||||
when the $SHELL exits, and know that nobody cares about
|
||||
them in the meantime. With redo, since the entire script
|
||||
runs inside a shell (and the shell might not exit until the
|
||||
very end of the build), we'd have to do the parallelism
|
||||
some other way.
|
||||
|
||||
I'm sure it's doable, however. One nice thing about redo
|
||||
is that the source code is so small compared to make: you
|
||||
can just rewrite it.
|
||||
|
||||
|
||||
# Can I convince a sub-redo or sub-make to *not* use parallel builds?
|
||||
|
||||
Yes. Put this in your .do script:
|
||||
|
||||
unset MAKEFLAGS
|
||||
|
||||
The child makes will then not have access to the jobserver,
|
||||
so will build serially instead.
|
||||
582
docs/FAQSemantics.md
Normal file
582
docs/FAQSemantics.md
Normal file
|
|
@ -0,0 +1,582 @@
|
|||
# Can I put all my rules in one big Redofile like make does?
|
||||
|
||||
One of my favourite features of redo is that it doesn't add any new syntax;
|
||||
the syntax of redo is *exactly* the syntax of sh... because sh is the program
|
||||
interpreting your .do file.
|
||||
|
||||
Also, it's surprisingly useful to have each build script in its own file;
|
||||
that way, you can declare a dependency on just that one build script instead
|
||||
of the entire Makefile, and you won't have to rebuild everything just
|
||||
because of a one-line Makefile change. (Some build tools avoid that same
|
||||
problem by tracking which variables and commands were used to do the build.
|
||||
But that's more complex, more error prone, and slower.)
|
||||
|
||||
See djb's [Target files depend on build scripts](http://cr.yp.to/redo/honest-script.html)
|
||||
article for more information.
|
||||
|
||||
However, if you really want to, you can simply create a
|
||||
default.do that looks something like this:
|
||||
|
||||
case $1 in
|
||||
*.o) ...compile a .o file... ;;
|
||||
myprog) ...link a program... ;;
|
||||
*) echo "no rule to build '$1'" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
Basically, default.do is the equivalent of a central
|
||||
Makefile in make. As of recent versions of redo, you can
|
||||
use either a single toplevel default.do (which catches
|
||||
requests for files anywhere in the project that don't have
|
||||
their own .do files) or one per directory, or any
|
||||
combination of the above. And you can put some of your
|
||||
targets in default.do and some of them in their own files.
|
||||
Lay it out in whatever way makes sense to you.
|
||||
|
||||
One more thing: if you put all your build rules in a single
|
||||
default.do, you'll soon discover that changing *anything*
|
||||
in that default.do will cause all your targets to rebuilt -
|
||||
because their .do file has changed. This is technically
|
||||
correct, but you might find it annoying. To work around
|
||||
it, try making your default.do look like this:
|
||||
|
||||
. ./default.od
|
||||
|
||||
And then put the above case statement in default.od
|
||||
instead. Since you didn't `redo-ifchange default.od`,
|
||||
changes to default.od won't cause everything to rebuild.
|
||||
|
||||
|
||||
# What are the parameters ($1, $2, $3) to a .do file?
|
||||
|
||||
NOTE: These definitions have changed since the earliest
|
||||
(pre-0.10) versions of redo. The new definitions match
|
||||
what djb's original redo implementation did.
|
||||
|
||||
$1 is the name of the target file.
|
||||
|
||||
$2 is the basename of the target, minus the extension, if
|
||||
any.
|
||||
|
||||
$3 is the name of a temporary file that will be renamed to
|
||||
the target filename atomically if your .do file returns a
|
||||
zero (success) exit code.
|
||||
|
||||
In a file called `chicken.a.b.c.do` that builds a file called
|
||||
`chicken.a.b.c`, $1 and $2 are `chicken.a.b.c`, and $3 is a
|
||||
temporary name like `chicken.a.b.c.tmp`. You might have expected
|
||||
$2 to be just `chicken`, but that's not possible, because
|
||||
redo doesn't know which portion of the filename is the
|
||||
"extension." Is it `.c`, `.b.c`, or `.a.b.c`?
|
||||
|
||||
.do files starting with `default.` are special; they can
|
||||
build any target ending with the given extension. So let's
|
||||
say we have a file named `default.c.do` building a file
|
||||
called `chicken.a.b.c`. $1 is `chicken.a.b.c`, $2 is `chicken.a.b`,
|
||||
and $3 is a temporary name like `chicken.a.b.c.tmp`.
|
||||
|
||||
You should use $1 and $2 only in constructing input
|
||||
filenames and dependencies; never modify the file named by
|
||||
$1 in your script. Only ever write to the file named by
|
||||
$3. That way redo can guarantee proper dependency
|
||||
management and atomicity. (For convenience, you can write
|
||||
to stdout instead of $3 if you want.)
|
||||
|
||||
For example, you could compile a .c file into a .o file
|
||||
like this, from a script named `default.o.do`:
|
||||
|
||||
redo-ifchange $2.c
|
||||
gcc -o $3 -c $2.c
|
||||
|
||||
|
||||
# Why not $FILE, $BASE, $OUT instead of $1, $2, $3?
|
||||
|
||||
That sounds tempting and easy, but one downside would be
|
||||
lack of backward compatibility with djb's original redo
|
||||
design.
|
||||
|
||||
Longer names aren't necessarily better. Learning the
|
||||
meanings of the three numbers doesn't take long, and over
|
||||
time, those extra few keystrokes can add up. And remember
|
||||
that Makefiles and perl have had strange one-character
|
||||
variable names for a long time. It's not at all clear that
|
||||
removing them is an improvement.
|
||||
|
||||
|
||||
# What happens to stdin/stdout/stderr?
|
||||
|
||||
As with make, stdin is not redirected. You're probably
|
||||
better off not using it, though, because especially with
|
||||
parallel builds, it might not do anything useful. We might
|
||||
change this behaviour someday since it's such a terrible
|
||||
idea for .do scripts to read from stdin.
|
||||
|
||||
As with make, stderr is also not redirected. You can use
|
||||
it to print status messages as your build proceeds.
|
||||
(Eventually, we might want to capture stderr so it's easier
|
||||
to look at the results of parallel builds, but this is
|
||||
tricky to do in a user-friendly way.)
|
||||
|
||||
Redo treats stdout specially: it redirects it to point at
|
||||
$3 (see previous question). That is, if your .do file
|
||||
writes to stdout, then the data it writes ends up in the
|
||||
output file. Thus, a really simple `chicken.do` file that
|
||||
contains only this:
|
||||
|
||||
echo hello world
|
||||
|
||||
will correctly, and atomically, generate an output file
|
||||
named `chicken` only if the echo command succeeds.
|
||||
|
||||
|
||||
# Isn't it confusing to capture stdout by default?
|
||||
|
||||
Yes, it is. It's unlike what almost any other program
|
||||
does, especially make, and it's very easy to make a
|
||||
mistake. For example, if you write in your script:
|
||||
|
||||
echo "Hello world"
|
||||
|
||||
it will go to the target file rather than to the screen.
|
||||
|
||||
A more common mistake is to run a program that writes to
|
||||
stdout by accident as it runs. When you do that, you'll
|
||||
produce your target on $3, but it might be intermingled
|
||||
with junk you wrote to stdout. redo is pretty good about
|
||||
catching this mistake, and it'll print a message like this:
|
||||
|
||||
redo zot.do wrote to stdout *and* created $3.
|
||||
redo ...you should write status messages to stderr, not stdout.
|
||||
redo zot: exit code 207
|
||||
|
||||
Despite the disadvantages, though, automatically capturing
|
||||
stdout does make certain kinds of .do scripts really
|
||||
elegant. The "simplest possible .do file" can be very
|
||||
short. For example, here's one that produces a sub-list
|
||||
from a list:
|
||||
|
||||
redo-ifchange filelist
|
||||
grep ^src/ filelist
|
||||
|
||||
redo's simplicity is an attempt to capture the "Zen of
|
||||
Unix," which has a lot to do with concepts like pipelines
|
||||
and stdout. Why should every program have to implement its
|
||||
own -o (output filename) option when the shell already has
|
||||
a redirection operator? Maybe if redo gets more popular,
|
||||
more programs in the world will be able to be even simpler
|
||||
than they are today.
|
||||
|
||||
By the way, if you're running some programs that might
|
||||
misbehave and write garbage to stdout instead of stderr
|
||||
(Informational/status messages always belong on stderr, not
|
||||
stdout! Fix your programs!), then just add this line to
|
||||
the top of your .do script:
|
||||
|
||||
exec >&2
|
||||
|
||||
That will redirect your stdout to stderr, so it works more
|
||||
like you expect.
|
||||
|
||||
|
||||
# Run redo-ifchange in a loop?
|
||||
|
||||
The obvious way to write a list of dependencies might be
|
||||
something like this:
|
||||
|
||||
for d in *.c; do
|
||||
redo-ifchange ${d%.c}.o
|
||||
done
|
||||
|
||||
But it turns out that's very non-optimal. First of all, it
|
||||
forces all your dependencies to be built in order
|
||||
(redo-ifchange doesn't return until it has finished
|
||||
building), which makes -j parallelism a lot less useful.
|
||||
And secondly, it forks and execs redo-ifchange over and
|
||||
over, which can waste CPU time unnecessarily.
|
||||
|
||||
A better way is something like this:
|
||||
|
||||
for d in *.c; do
|
||||
echo ${d%.c}.o
|
||||
done |
|
||||
xargs redo-ifchange
|
||||
|
||||
That only runs redo-ifchange once (or maybe a few times, if
|
||||
there are really a *lot* of dependencies and xargs has to
|
||||
split it up), which saves fork/exec time and allows for
|
||||
parallelism.
|
||||
|
||||
|
||||
# If a target is identical after rebuilding, how do I prevent dependents from being rebuilt?
|
||||
|
||||
For example, running ./configure creates a bunch of files including
|
||||
config.h, and config.h might or might not change from one run to the next.
|
||||
We don't want to rebuild everything that depends on config.h if config.h is
|
||||
identical.
|
||||
|
||||
With `make`, which makes build decisions based on timestamps, you would
|
||||
simply have the ./configure script write to config.h.new, then only
|
||||
overwrite config.h with that if the two files are different.
|
||||
However, that's a bit tedious.
|
||||
|
||||
With `redo`, there's an easier way. You can have a
|
||||
config.do script that looks like this:
|
||||
|
||||
redo-ifchange autogen.sh *.ac
|
||||
./autogen.sh
|
||||
./configure
|
||||
cat config.h configure Makefile | redo-stamp
|
||||
|
||||
Now any of your other .do files can depend on a target called
|
||||
`config`. `config` gets rebuilt automatically if any of
|
||||
your autoconf input files are changed (or if someone does
|
||||
`redo config` to force it). But because of the call to
|
||||
redo-stamp, `config` is only considered to have changed if
|
||||
the contents of config.h, configure, or Makefile are
|
||||
different than they were before.
|
||||
|
||||
(Note that you might actually want to break this .do up into a
|
||||
few phases: for example, one that runs aclocal, one that
|
||||
runs autoconf, and one that runs ./configure. That way
|
||||
your build can always do the minimum amount of work
|
||||
necessary.)
|
||||
|
||||
|
||||
# Why does 'redo target' redo even unchanged targets?
|
||||
|
||||
When you run `make target`, make first checks the
|
||||
dependencies of target; if they've changed, then it
|
||||
rebuilds target. Otherwise it does nothing.
|
||||
|
||||
redo is a little different. It splits the build into two
|
||||
steps. `redo target` is the second step; if you run that
|
||||
at the command line, it just runs the .do file, whether it
|
||||
needs it or not.
|
||||
|
||||
If you really want to only rebuild targets that have
|
||||
changed, you can run `redo-ifchange target` instead.
|
||||
|
||||
The reasons I like this arrangement come down to semantics:
|
||||
|
||||
- "make target" implies that if target exists, you're done;
|
||||
conversely, "redo target" in English implies you really
|
||||
want to *redo* it, not just sit around.
|
||||
|
||||
- If this weren't the rule, `redo` and `redo-ifchange`
|
||||
would mean the same thing, which seems rather confusing.
|
||||
|
||||
- If `redo` could refuse to run a .do script, you would
|
||||
have no easy one-line way to force a particular target to
|
||||
be rebuilt. You'd have to remove the target and *then*
|
||||
redo it, which is more typing. On the other hand, nobody
|
||||
actually types "redo foo.o" if they honestly think foo.o
|
||||
doesn't need rebuilding.
|
||||
|
||||
- For "contentless" targets like "test" or "clean", it would
|
||||
be extremely confusing if they refused to run just
|
||||
because they ran successfully last time.
|
||||
|
||||
In make, things get complicated because it doesn't
|
||||
differentiate between these two modes. Makefile rules
|
||||
with no dependencies run every time, *unless* the target
|
||||
exists, in which case they run never, *unless* the target
|
||||
is marked ".PHONY", in which case they run every time. But
|
||||
targets that *do* have dependencies follow totally
|
||||
different rules. And all this is needed because there's no
|
||||
way to tell make, "Listen, I just really want you to run
|
||||
the rules for this target *right now*."
|
||||
|
||||
With redo, the semantics are really simple to explain. If
|
||||
your brain has already been fried by make, you might be
|
||||
surprised by it at first, but once you get used to it, it's
|
||||
really much nicer this way.
|
||||
|
||||
|
||||
# Can I write .do files in my favourite language, not sh?
|
||||
|
||||
Yes. If the first line of your .do file starts with the
|
||||
magic "#!/" sequence (eg. `#!/usr/bin/python`), then redo
|
||||
will execute your script using that particular interpreter.
|
||||
|
||||
Note that this is slightly different from normal Unix
|
||||
execution semantics. redo never execs your script directly;
|
||||
it only looks for the "#!/" line. The main reason for this
|
||||
is so that your .do scripts don't have to be marked
|
||||
executable (chmod +x). Executable .do scripts would
|
||||
suggest to users that they should run them directly, and
|
||||
they shouldn't; .do scripts should always be executed
|
||||
inside an instance of redo, so that dependencies can be
|
||||
tracked correctly.
|
||||
|
||||
WARNING: If your .do script *is* written in Unix sh, we
|
||||
recommend *not* including the `#!/bin/sh` line. That's
|
||||
because there are many variations of /bin/sh, and not all
|
||||
of them are POSIX compliant. redo tries pretty hard to
|
||||
find a good default shell that will be "as POSIXy as
|
||||
possible," and if you override it using #!/bin/sh, you lose
|
||||
this benefit and you'll have to worry more about
|
||||
portability.
|
||||
|
||||
|
||||
# Can a single .do script generate multiple outputs?
|
||||
|
||||
FIXME: Yes, but this is a bit imperfect.
|
||||
|
||||
For example, compiling a .java file produces a bunch of .class
|
||||
files, but exactly which files? It depends on the content
|
||||
of the .java file. Ideally, we would like to allow our .do
|
||||
file to compile the .java file, note which .class files
|
||||
were generated, and tell redo about it for dependency
|
||||
checking.
|
||||
|
||||
However, this ends up being confusing; if myprog depends
|
||||
on foo.class, we know that foo.class was generated from
|
||||
bar.java only *after* bar.java has been compiled. But how
|
||||
do you know, the first time someone asks to build myprog,
|
||||
where foo.class is supposed to come from?
|
||||
|
||||
So we haven't thought about this enough yet.
|
||||
|
||||
Note that it's *okay* for a .do file to produce targets
|
||||
other than the advertised one; you just have to be careful.
|
||||
You could have a default.javac.do that runs 'javac
|
||||
$2.java', and then have your program depend on a bunch of .javac
|
||||
files. Just be careful not to depend on the .class files
|
||||
themselves, since redo won't know how to regenerate them.
|
||||
|
||||
This feature would also be useful, again, with ./configure:
|
||||
typically running the configure script produces several
|
||||
output files, and it would be nice to declare dependencies
|
||||
on all of them.
|
||||
|
||||
|
||||
# Should I use environment variables to affect my build?
|
||||
|
||||
Directly using environment variables is a bad idea because you can't declare
|
||||
dependencies on them. Also, if there were a file that contained a set of
|
||||
variables that all your .do scripts need to run, then `redo` would have to
|
||||
read that file every time it starts (which is frequently, since it's
|
||||
recursive), and that could get slow.
|
||||
|
||||
Luckily, there's an alternative. Once you get used to it, this method is
|
||||
actually much better than environment variables, because it runs faster
|
||||
*and* it's easier to debug.
|
||||
|
||||
For example, djb often uses a computer-generated script called `compile` for
|
||||
compiling a .c file into a .o file. To generate the `compile` script, we
|
||||
create a file called `compile.do`:
|
||||
|
||||
redo-ifchange config.sh
|
||||
. ./config.sh
|
||||
echo "gcc -c -o \$3 \$2.c $CFLAGS" >$3
|
||||
chmod a+x $3
|
||||
|
||||
Then, your `default.o.do` can simply look like this:
|
||||
|
||||
redo-ifchange compile $2.c
|
||||
./compile $1 $2 $3
|
||||
|
||||
This is not only elegant, it's useful too. With make, you have to always
|
||||
output everything it does to stdout/stderr so you can try to figure out
|
||||
exactly what it was running; because this gets noisy, some people write
|
||||
Makefiles that deliberately hide the output and print something friendlier,
|
||||
like "Compiling hello.c". But then you have to guess what the compile
|
||||
command looked like.
|
||||
|
||||
With redo, the command *is* `./compile hello.c`, which looks good when
|
||||
printed, but is also completely meaningful. Because it doesn't depend on
|
||||
any environment variables, you can just run `./compile hello.c` to reproduce
|
||||
its output, or you can look inside the `compile` file to see exactly what
|
||||
command line is being used.
|
||||
|
||||
As a bonus, all the variable expansions only need to be done once: when
|
||||
generating the ./compile program. With make, it would be recalculating
|
||||
expansions every time it compiles a file. Because of the
|
||||
way make does expansions as macros instead of as normal
|
||||
variables, this can be slow.
|
||||
|
||||
|
||||
# Example default.o.do for both C and C++ source?
|
||||
|
||||
We can upgrade the compile.do from the previous answer to
|
||||
look something like this:
|
||||
|
||||
redo-ifchange config.sh
|
||||
. ./config.sh
|
||||
cat <<-EOF
|
||||
[ -e "\$2.cc" ] && EXT=.cc || EXT=.c
|
||||
gcc -o "\$3" -c "\$1\$EXT" -Wall $CFLAGS
|
||||
EOF
|
||||
chmod a+x "$3"
|
||||
|
||||
Isn't it expensive to have ./compile doing this kind of test for every
|
||||
single source file? Not really. Remember, if you have two implicit rules
|
||||
in make:
|
||||
|
||||
%.o: %.cc
|
||||
gcc ...
|
||||
|
||||
%.o: %.c
|
||||
gcc ...
|
||||
|
||||
Then it has to do all the same checks. Except make has even *more* implicit
|
||||
rules than that, so it ends up trying and discarding lots of possibilities
|
||||
before it actually builds your program. Is there a %.s? A
|
||||
%.cpp? A %.pas? It needs to look for *all* of them, and
|
||||
it gets slow. The more implicit rules you have, the slower
|
||||
make gets.
|
||||
|
||||
In redo, it's not implicit at all; you're specifying exactly how to
|
||||
decide whether it's a C program or a C++ program, and what to do in each
|
||||
case. Plus you can share the two gcc command lines between the two rules,
|
||||
which is hard in make. (In GNU make you can use macro functions, but the
|
||||
syntax for those is ugly.)
|
||||
|
||||
|
||||
# Can I just rebuild just part of a project?
|
||||
|
||||
Absolutely! Although `redo` runs "top down" in the sense of one .do file
|
||||
calling into all its dependencies, you can start at any point in the
|
||||
dependency tree that you want.
|
||||
|
||||
Unlike recursive make, no matter which subdir of your project you're in when
|
||||
you start, `redo` will be able to build all the dependencies in the right
|
||||
order.
|
||||
|
||||
Unlike non-recursive make, you don't have to jump through any strange hoops
|
||||
(like adding, in each directory, a fake Makefile that does `make -C ${TOPDIR}`
|
||||
back up to the main non-recursive Makefile). redo just uses `filename.do`
|
||||
to build `filename`, or uses `default*.do` if the specific `filename.do`
|
||||
doesn't exist.
|
||||
|
||||
When running any .do file, `redo` makes sure its current directory is set to
|
||||
the directory where the .do file is located. That means you can do this:
|
||||
|
||||
redo ../utils/foo.o
|
||||
|
||||
And it will work exactly like this:
|
||||
|
||||
cd ../utils
|
||||
redo foo.o
|
||||
|
||||
In make, if you run
|
||||
|
||||
make ../utils/foo.o
|
||||
|
||||
it means to look in ./Makefile for a rule called
|
||||
../utils/foo.o... and it probably doesn't have such a
|
||||
rule. On the other hand, if you run
|
||||
|
||||
cd ../utils
|
||||
make foo.o
|
||||
|
||||
it means to look in ../utils/Makefile and look for a rule
|
||||
called foo.o. And that might do something totally
|
||||
different! redo combines these two forms and does
|
||||
the right thing in both cases.
|
||||
|
||||
Note: redo will always change to the directory containing
|
||||
the .do file before trying to build it. So if you do
|
||||
|
||||
redo ../utils/foo.o
|
||||
|
||||
the ../utils/default.o.do file will be run with its current directory set to
|
||||
../utils. Thus, the .do file's runtime environment is
|
||||
always reliable.
|
||||
|
||||
On the other hand, if you had a file called ../default.o.do,
|
||||
but there was no ../utils/default.o.do, redo would select
|
||||
../default.o.do as the best matching .do file. It would
|
||||
then run with its current directory set to .., and tell
|
||||
default.o.do to create an output file called "utils/foo.o"
|
||||
(that is, foo.o, with a relative path explaining how to
|
||||
find foo.o when you're starting from the directory
|
||||
containing the .do file).
|
||||
|
||||
That sounds a lot more complicated than it is. The results
|
||||
are actually very simple: if you have a toplevel
|
||||
default.o.do, then all your .o files will be compiled with
|
||||
$PWD set to the top level, and all the .o filenames passed
|
||||
as relative paths from $PWD. That way, if you use relative
|
||||
paths in -I and -L gcc options (for example), they will
|
||||
always be correct no matter where in the hierarchy your
|
||||
source files are.
|
||||
|
||||
|
||||
# Can I put my .o files in a different directory from my .c files?
|
||||
|
||||
Yes. There's nothing in redo that assumes anything about
|
||||
the location of your source files. You can do all sorts of
|
||||
interesting tricks, limited only by your imagination. For
|
||||
example, imagine that you have a toplevel default.o.do that looks
|
||||
like this:
|
||||
|
||||
ARCH=${1#out/}
|
||||
ARCH=${ARCH%%/*}
|
||||
SRC=${1#out/$ARCH/}
|
||||
redo-ifchange $SRC.c
|
||||
$ARCH-gcc -o $3 -c $SRC.c
|
||||
|
||||
If you run `redo out/i586-mingw32msvc/path/to/foo.o`, then
|
||||
the above script would end up running
|
||||
|
||||
i586-mingw32msvc-gcc -o $3 -c path/to/foo.c
|
||||
|
||||
You could also choose to read the compiler name or options from
|
||||
out/$ARCH/config.sh, or config.$ARCH.sh, or use any other
|
||||
arrangement you want.
|
||||
|
||||
You could use the same technique to have separate build
|
||||
directories for out/debug, out/optimized, out/profiled, and so on.
|
||||
|
||||
|
||||
# Can my filenames have spaces in them?
|
||||
|
||||
Yes, unlike with make. For historical reasons, the Makefile syntax doesn't
|
||||
support filenames with spaces; spaces are used to separate one filename from
|
||||
the next, and there's no way to escape these spaces.
|
||||
|
||||
Since redo just uses sh, which has working escape characters and
|
||||
quoting, it doesn't have this problem.
|
||||
|
||||
|
||||
# Does redo care about the differences between tabs and spaces?
|
||||
|
||||
No.
|
||||
|
||||
|
||||
# What if my .c file depends on a generated .h file?
|
||||
|
||||
This problem arises as follows. foo.c includes config.h, and config.h is
|
||||
created by running ./configure. The second part is easy; just write a
|
||||
config.h.do that depends on the existence of configure (which is created by
|
||||
configure.do, which probably runs autoconf).
|
||||
|
||||
The first part, however, is not so easy. Normally, the headers that a C
|
||||
file depends on are detected as part of the compilation process. That works
|
||||
fine if the headers, themselves, don't need to be generated first. But if
|
||||
you do
|
||||
|
||||
redo foo.o
|
||||
|
||||
There's no way for redo to *automatically* know that compiling foo.c
|
||||
into foo.o depends on first generating config.h.
|
||||
|
||||
Since most .h files are *not* auto-generated, the easiest
|
||||
thing to do is probably to just add a line like this to
|
||||
your default.o.do:
|
||||
|
||||
redo-ifchange config.h
|
||||
|
||||
Sometimes a specific solution is much easier than a general
|
||||
one.
|
||||
|
||||
If you really want to solve the general case,
|
||||
[djb has a solution for his own
|
||||
projects](http://cr.yp.to/redo/honest-nonfile.html), which is a simple
|
||||
script that looks through C files to pull out #include lines. He assumes
|
||||
that `#include <file.h>` is a system header (thus not subject to being
|
||||
built) and `#include "file.h"` is in the current directory (thus easy to
|
||||
find). Unfortunately this isn't really a complete
|
||||
solution, but at least it would be able to redo-ifchange a
|
||||
required header before compiling a program that requires
|
||||
that header.
|
||||
52
docs/GettingStarted.md
Normal file
52
docs/GettingStarted.md
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# Prerequisites
|
||||
|
||||
Currently, this version of redo requires python2.7 and the python2.7 sqlite3 module.
|
||||
Optional, but recommended, is the
|
||||
[setproctitle](http://code.google.com/p/py-setproctitle/) module, which makes your
|
||||
`ps` output prettier.
|
||||
|
||||
In modern versions of Debian, sqlite3 is already part of the python2.7 package.
|
||||
You can install the prerequisites like this:
|
||||
```sh
|
||||
sudo apt-get install python2.7 python-setproctitle
|
||||
```
|
||||
(If you have install instructions for other OSes, please add them here :))
|
||||
|
||||
|
||||
# Clone, compile, and test redo
|
||||
|
||||
You can run redo without installing it, like this:
|
||||
```sh
|
||||
git clone https://github.com/apenwarr/redo
|
||||
cd redo
|
||||
./redo -j10 test
|
||||
```
|
||||
|
||||
If the tests pass, you can either add $PWD/redo to your PATH, or install
|
||||
redo on your system. To install for all users, put it in /usr/local:
|
||||
|
||||
```sh
|
||||
PREFIX=/usr/local sudo ./redo install
|
||||
```
|
||||
|
||||
Or to install it just for yourself (without needing root access), put it in
|
||||
your home directory:
|
||||
```sh
|
||||
PREFIX=$HOME ./redo install
|
||||
```
|
||||
|
||||
|
||||
# Pre-compiled packages
|
||||
|
||||
## MacOS
|
||||
|
||||
redo is available from the [Homebrew](https://brew.sh/) project:
|
||||
|
||||
brew install redo
|
||||
|
||||
## Linux
|
||||
|
||||
Various linux distributions include redo under different names. Most of the
|
||||
packages are unfortunately obsolete and don't contain the most recent bug
|
||||
fixes. At this time (late 2018), we recommend using the latest tagged
|
||||
version [from github](https://github.com/apenwarr/redo).
|
||||
329
docs/Roadmap.md
Normal file
329
docs/Roadmap.md
Normal file
|
|
@ -0,0 +1,329 @@
|
|||
We believe that, unlike most programs, it's actually possible to "finish"
|
||||
redo, in the sense of eventually not needing to extend its semantics or add
|
||||
new features. That's because redo is a pretty low-level system that just
|
||||
provides some specific features (dependency checking, parallelism, log
|
||||
linearization, inter-process locking). It's the job of your build scripts
|
||||
to tie those features together in the way you want.
|
||||
|
||||
`make` has its own imperative syntax, which creates a temptation to add
|
||||
new built-in functions and syntax extensions. In more
|
||||
"declarative" build systems, there's a constant need to write new extension
|
||||
modules or features in order to create functionality that wasn't available
|
||||
declaratively. redo avoids that by using a turing-complete language
|
||||
to run your builds. You should be able to build anything at all with redo,
|
||||
just by writing your .do scripts the way you want.
|
||||
|
||||
Thus, the only things that need to be added to redo (other than portability
|
||||
and bug fixes, which will likely be needed forever) are to fix gaps in
|
||||
redo's model that prevent you from getting your work done. This document
|
||||
describes the gaps we're currently aware of.
|
||||
|
||||
Note that all of the items in this document are still unimplemented. In
|
||||
most cases, that's because we haven't yet settled on a final design, and
|
||||
it's still open to discussion. The place to discuss design issues is the
|
||||
[mailing list](../Contributing/#mailing-list).
|
||||
|
||||
|
||||
### default.do search path, and separated build directories
|
||||
|
||||
One of the most controversial topics in redo development, and for developers
|
||||
trying to use redo, is: where do you put all those .do scripts?
|
||||
|
||||
redo searches hierarchically up the directory tree from a target's filename,
|
||||
hoping to find a `default*.do` file that will match, and then uses the first
|
||||
one it finds. This method is rather elegant when it works. But many
|
||||
developers would like to put their output files into a separate directory
|
||||
from their source files, and that output directory might not be a
|
||||
subdirectory of the main project (for example, if the main project is on a
|
||||
read-only filesystem).
|
||||
|
||||
There are already a few ways to make this work, such as placing a single
|
||||
`default.do` "proxy" or "delegation" script at the root of the output
|
||||
directory, which will bounce requests to .do files it finds elsewhere. One
|
||||
nice thing about this feature is it doesn't require any changes to redo
|
||||
itself; redo already knows how to call your toplevel default.do script.
|
||||
However, some people find the delegation script to be inelegant and
|
||||
complicated.
|
||||
|
||||
Other options include searching inside a known subdirectory name (eg.
|
||||
`do/`), which could be a symlink; or adding a `.dopath` file which tells
|
||||
redo to search elsewhere.
|
||||
|
||||
So far, we haven't settled on the best design, and discussion is welcome.
|
||||
In the meantime, you can write a delegation script (TODO: link to example)
|
||||
for your project. Because this requires no special redo features, it's
|
||||
unlikely to break in some later version of redo, even if we add a new
|
||||
method.
|
||||
|
||||
|
||||
### .do files that produce directories
|
||||
|
||||
Sometimes you want a .do file to produce multiple output files in a single
|
||||
step. One example is an autoconf `./configure` script, which might produce
|
||||
multiple files. Or, for example, look at the [LaTeX typesetting
|
||||
example](../cookbook/latex/) in the redo cookbook.
|
||||
|
||||
In the purest case, generating multiple outputs from a single .do file
|
||||
execution violates the redo semantics. The design of redo calls for
|
||||
generating *one* output file from *zero or more* input files. And most of
|
||||
the time, that works fine. But sometimes it's not enough.
|
||||
|
||||
Currently (like in the LaTeX example linked above) we need to resolve this
|
||||
problem by taking advantage of "side effects" in redo: creating a set of
|
||||
files that are unknown to redo, but sit alongside the "known" files in the
|
||||
filesystem. But this has the annoying limitation that you cannot
|
||||
redo-ifchange directly on the file you want, if it was generated this way.
|
||||
For example, if `runconfig.do` generates `Makefile` and `config.h`, you
|
||||
must not `redo-ifchange config.h` directly; there is no .do file for
|
||||
`config.h`. You must `redo-ifchange runconfig` and then *use*
|
||||
`config.h`.
|
||||
|
||||
(There are workarounds for that workaround: for example, `runconfig.do`
|
||||
could put all its output files in a `config/` directory, and then you could
|
||||
have a `config.h.do` that does `redo-ifchange runconfig` and `cp
|
||||
config/config.h $3`. Then other scripts can `redo-ifchange config.h`
|
||||
without knowing any more about it. But this method gets tedious.)
|
||||
|
||||
One suggestion for improving the situation would be to teach redo about
|
||||
"directory" targets. For example, maybe we have a `config.dir.do` that
|
||||
runs `./configure` and produces files in a directory named `config`. The
|
||||
`.dir.do` magic suffix tells redo that if someone asks for
|
||||
`config/config.h`, it must first try to instantiate the directory named
|
||||
`config` (using `config.dir.do`), and only then try to depend on the file
|
||||
inside that directory.
|
||||
|
||||
There are a number of holes in this design, however. Notably, it's not
|
||||
obvious how redo should detect when to activate the magic directory feature.
|
||||
It's easy when there is a file named `config.dir.do`, but much less obvious
|
||||
for a file like `default.dir.do` that can construct certain directory types,
|
||||
but it's not advertised which ones.
|
||||
|
||||
This particular cure may turn out to be worse than the disease.
|
||||
|
||||
|
||||
### Per-target-directory .redo database
|
||||
|
||||
An unexpectedly very useful feature of redo is the ability to "redo from
|
||||
anywhere" and get the same results:
|
||||
```shell
|
||||
$ cd /a/b/c
|
||||
$ redo /x/y/z/all
|
||||
```
|
||||
should have the same results as
|
||||
```shell
|
||||
$ cd /x/y/z
|
||||
$ redo all
|
||||
```
|
||||
|
||||
Inside a single project, this already works. But as redo gets used more
|
||||
widely, and in particular when you have multiple redo-using projects that
|
||||
want to refer to other redo-using projects, redo can get confused about
|
||||
where to put its `.redo` state database. Normally, it goes into a directory
|
||||
called `$REDO_BASE`, the root directory of your project. But if a .do
|
||||
script refers to a directory outside or beside the root, this method doesn't
|
||||
work, and redo gets the wrong file state information.
|
||||
|
||||
Further complications arise in the case of symlinks. For example, if you
|
||||
ask redo to build `x/y/z/file` but `y` is a symlink to `q`, then redo will
|
||||
effectively end up replacing `x/q/z/file` when it replces `x/y/z/file`,
|
||||
since they're the same. If someone then does `redo-ifchange x/q/z/file`,
|
||||
redo may become confused about why that file has "unexpectedly" changed.
|
||||
|
||||
The fix for both problems is simple: put one `.redo` database in every
|
||||
directory that contains target files. The `.redo` in each directory
|
||||
contains information only about the targets in that directory. As a result,
|
||||
`x/y/z/file` and `x/q/z/file` will share the same state database,
|
||||
`x/q/z/.redo`, and building either target will update the state database's
|
||||
understanding of the file called `file` in the same directory, and there
|
||||
will be no confusion.
|
||||
|
||||
Similarly, one redo-using project can refer to targets in another redo-using
|
||||
project with no problem, because redo will no longer have the concept of a
|
||||
`$REDO_BASE`, so there is no way to talk about targets "outside" the
|
||||
`$REDO_BASE`.
|
||||
|
||||
Note that there is no reason to maintain a `.redo` state database in
|
||||
*source* directories (which might be read-only), only target directories.
|
||||
This is because we store `stat(2)` information for each dependency anyway, so
|
||||
it's harmless if multiple source filenames are aliases for the same
|
||||
underlying content.
|
||||
|
||||
|
||||
### redo-{sources,targets,ood} should take a list of targets
|
||||
|
||||
With the above change to a per-target-directory `.redo` database, the
|
||||
original concept of the `redo-sources`, `redo-targets`, and `redo-ood`
|
||||
commands needs to change. Currently they're defined to list "all" the
|
||||
sources, targets, and out-of-date targets, respectively. But when there is
|
||||
no single database reflecting the entire filesystem, the concept of "all"
|
||||
becomes fuzzy.
|
||||
|
||||
We'll have to change these programs to refer to "all (recursive)
|
||||
dependencies of targets in the current directory" by default, or of all
|
||||
targets listed on the command line otherwise. This is probably more useful
|
||||
than the current behaviour anyway, since in a large project, one rarely
|
||||
wants to see a complete list of all sources and targets.
|
||||
|
||||
|
||||
### Deprecating "stdout capture" behaviour
|
||||
|
||||
The [original design for redo](http://cr.yp.to/redo.html) specified that a
|
||||
.do script could produce its output either by writing to stdout, or by
|
||||
writing to the file named by the `$3` variable.
|
||||
|
||||
Experience has shown that most developers find this very confusing. In
|
||||
particular, results are undefined if you write to *both* stdout and `$3`.
|
||||
Also, many programs (including `make`!) write their log messages to stdout
|
||||
when they should write to stderr, so many .do scripts need to start with
|
||||
`exec >&2` to avoid confusion.
|
||||
|
||||
In retrospect, automatically capturing stdout was probably a bad idea. .do
|
||||
scripts should intentionally redirect to `$3`. To enforce this, we could
|
||||
have redo report an error whenever a .do script returns after writing to its
|
||||
stdout. For backward compatibility, we could provide a command-line option
|
||||
to downgrade the error to a warning.
|
||||
|
||||
|
||||
### Deprecating environment variable sharing
|
||||
|
||||
In redo, it's considered a poor practice to pass environment variables (and
|
||||
other process attributes, like namespaces) from one .do script to another.
|
||||
This is because running `redo-ifchange /path/to/file` should always run
|
||||
`file`'s .do script with exactly the same settings, whether you do it from
|
||||
the toplevel from from deep inside a tree of dependencies. If an
|
||||
environment variable set in one .do script can change what's seen by an
|
||||
inner .do script, this breaks the dependency mechanism and makes builds less
|
||||
repeatable.
|
||||
|
||||
To make it harder to do this by accident, redo could intentionally wipe all
|
||||
but a short whitelist of allowed environment variables before running any
|
||||
.do script.
|
||||
|
||||
As a bonus, by never sharing any state outside the filesystem, it becomes
|
||||
much more possible to make a "distributed redo" that builds different
|
||||
targets on different physical computers.
|
||||
|
||||
|
||||
### redo-recheck command
|
||||
|
||||
Normally, redo only checks any given file dependency at most once per
|
||||
session, in order to reduce the number of system calls executed, thus
|
||||
greatly speeding up incremental builds. As a result, `redo-ifchange` of the
|
||||
same target will only execute the relevant .do script at most once per
|
||||
session.
|
||||
|
||||
In some situations, notably integration tests, we want to force redo to
|
||||
re-check more often. Right now there's a hacky script called
|
||||
`t/flush-cache` in the redo distribution which does this, but it relies on
|
||||
specific knowledge of the .redo directory's database format, which means it
|
||||
only works in this specific version of redo; this prevents the integration
|
||||
tests from running (and thus checking compatibility with) competing redo
|
||||
implementations.
|
||||
|
||||
If we standardized a `redo-recheck` command, which would flush the cache for
|
||||
the targets given on the command line, and all of their dependencies, this
|
||||
sort of integration test could work across multiple redo versions. For redo
|
||||
versions which don't bother caching, `redo-recheck` could be a null
|
||||
operation.
|
||||
|
||||
|
||||
### tty input
|
||||
|
||||
Right now, redo only allows a .do file to request input from the user's
|
||||
terminal if using `--no-log` and *not* using the `-j` option. Terminal
|
||||
input is occasionally useful for `make config` interfaces, but parallelism
|
||||
and log linearization make the console too cluttered for a UI to work.
|
||||
|
||||
The ninja build system has a [console
|
||||
pool](https://ninja-build.org/manual.html#_the_literal_console_literal_pool)
|
||||
that can contain up to one job at a time. When a job is in the console
|
||||
pool, it takes over the console entirely.
|
||||
|
||||
We could probably implement something similar in redo by using POSIX job
|
||||
control features, which suspend subtasks whenever they try to read
|
||||
from the tty. If we caught the suspension signal and acquired a lock, we
|
||||
could serialize console access.
|
||||
|
||||
Whether the complexity of this feature is worthwhile is unclear. Maybe it
|
||||
makes more sense just to have a './configure' script that runs outside the
|
||||
redo environment, but still can call into redo-ifchange if needed.
|
||||
|
||||
|
||||
### redo-lock command
|
||||
|
||||
Because it supports parallelism via recursion, redo automatically handles
|
||||
inter-process locking so that only one instance of redo can try to build a
|
||||
given target at a time.
|
||||
|
||||
This sort of locking turns out to be very useful, but there are a few
|
||||
situations where requiring redo to "build a target by calling a .do file" in
|
||||
order to acquire a lock becomes awkward.
|
||||
|
||||
For example, imagine redo is being used to call into `make` to run arbitrary
|
||||
`Makefile` targets. `default.make.do` might look like this:
|
||||
```sh
|
||||
make "$2"
|
||||
```
|
||||
|
||||
redo will automatically prevent two copies of `redo all.make` from running
|
||||
at once. However, if someone runs `redo all.make myprogram.make`, then two
|
||||
copies of `make` will execute at once. This *might* be harmless, but if the
|
||||
`all` target in the `Makefile` has a dependency on `myprogram`, then we will
|
||||
actually end up implicitly building `myprogram` from two places at once:
|
||||
from the `myprogram` part of `all.make` and from `myprogram.make`.
|
||||
|
||||
In hairy situations like that, it would be nice to serialize all access
|
||||
inside `default.make.do`, perhaps like this:
|
||||
```sh
|
||||
redo-lock make.lock make "$2"
|
||||
```
|
||||
|
||||
This would create a redo-style lock on the (virtual) file `make.lock`, but
|
||||
then instead of trying to `redo make.lock`, it would run the given command,
|
||||
in this case `make "$2"`.
|
||||
|
||||
It's unclear whether this feature is really a good idea. There are other
|
||||
(convoluted) ways to achieve the same goal. Nevertheless, it would be easy
|
||||
enough to implement. And redo versions that don't support parallelism could
|
||||
just make redo-lock a no-op, since they guarantee serialization in all cases
|
||||
anyway.
|
||||
|
||||
|
||||
### Include a (minimal) POSIX shell
|
||||
|
||||
A common problem when writing build scripts, both in `make` and in redo, is
|
||||
gratuitous incompatibility between all the available POSIX-like unix shells.
|
||||
Nowadays, most shells support [various pure POSIX sh
|
||||
features](https://apenwarr.ca/log/20110228), but there are always glitches.
|
||||
In some cases, POSIX doesn't define the expected behaviour for certain
|
||||
situations. In others, shells like `bash` try to "improve" things by
|
||||
changing the syntax in non-POSIX ways. Or maybe they just add new
|
||||
backward-compatible features, which you then rely on accidentally because
|
||||
you only tested your scripts with `bash`.
|
||||
|
||||
redo on Windows using something like [MSYS](http://www.mingw.org/wiki/msys)
|
||||
is especially limited by the lack of (and oddity of) available unix tools.
|
||||
|
||||
To avoid all these portability problems for .do script maintainers, we might
|
||||
consider bundling redo with a particular (optional) sh implementation, and
|
||||
maybe also unix-like tools, that it will use by default. An obvious
|
||||
candidate would be busybox, which has a win32 version called
|
||||
[busybox-w32](https://frippery.org/busybox/).
|
||||
|
||||
|
||||
### redoconf
|
||||
|
||||
redo is fundamentally a low-level tool that doesn't know as much about
|
||||
compiling specific programming languages as do higher-level tools like
|
||||
[cmake](https://cmake.org/).
|
||||
|
||||
Similarly, `make` doesn't know much about specific programming languages
|
||||
(and what it does know is hopelessly out of date, but cannot be deleted or
|
||||
updated because it would break backward compatibility with old Makefiles).
|
||||
This is why `autoconf` and `automake` were created: to automatically fill in
|
||||
the language- and platform-specific blanks, while letting `make` still
|
||||
handle executing the low level instructions.
|
||||
|
||||
It might be useful to have a redo-native autoconf/automake-like system,
|
||||
although you can already use autoconf with redo, so this might not be
|
||||
essential.
|
||||
9
docs/all.do
Normal file
9
docs/all.do
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
redo-ifchange doc.list
|
||||
sed 's/\.md/.1/' <doc.list |
|
||||
xargs redo-ifchange
|
||||
|
||||
# mkdocs foolishly tries to process every file in this directory, which
|
||||
# leads it to try to open temp files produced by the above redo-ifchange
|
||||
# if it runs in parallel with those jobs. So don't run it until they
|
||||
# finish.
|
||||
redo-ifchange mkdocs
|
||||
2
docs/clean.do
Normal file
2
docs/clean.do
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
redo cookbook/clean
|
||||
rm -f *~ .*~ *.1 t/*.1 md-to-man *.tmp t/*.tmp *.html */*.html *.list
|
||||
3
docs/cookbook/all.do
Normal file
3
docs/cookbook/all.do
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
for d in */all.do; do
|
||||
echo "${d%.do}"
|
||||
done | xargs redo-ifchange
|
||||
3
docs/cookbook/clean.do
Normal file
3
docs/cookbook/clean.do
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
for d in */clean.do; do
|
||||
echo "${d%.do}"
|
||||
done | xargs redo
|
||||
5
docs/cookbook/defaults/.gitignore
vendored
Normal file
5
docs/cookbook/defaults/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
date
|
||||
version
|
||||
include/version.h
|
||||
version.py
|
||||
test.txt
|
||||
2
docs/cookbook/defaults/all.do
Normal file
2
docs/cookbook/defaults/all.do
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
redo-ifchange test.txt version.py \
|
||||
test.py include/version.h
|
||||
2
docs/cookbook/defaults/clean.do
Normal file
2
docs/cookbook/defaults/clean.do
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
rm -f date version include/version.h \
|
||||
version.py test.txt *~ .*~
|
||||
3
docs/cookbook/defaults/date.do
Normal file
3
docs/cookbook/defaults/date.do
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
date +'%Y-%m-%d' >$3
|
||||
redo-always
|
||||
redo-stamp <$3
|
||||
25
docs/cookbook/defaults/default.do
Normal file
25
docs/cookbook/defaults/default.do
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# $1 is the target name, eg. test.txt
|
||||
# $2 in the same as $1. We'll talk about
|
||||
# that in a later example.
|
||||
# $3 is the temporary output file we should
|
||||
# create. If this script is successful,
|
||||
# redo will atomically replace $1 with $3.
|
||||
|
||||
if [ -e "$1.in" ]; then
|
||||
# if a .in file exists, then do some
|
||||
# text substitution.
|
||||
#
|
||||
# Remember, the user asks redo to build
|
||||
# a particular *target* name. It's the .do
|
||||
# file's job to figure out what source file(s)
|
||||
# to use to generate the target.
|
||||
redo-ifchange "$1.in" version date
|
||||
read VERSION <version
|
||||
read DATE <date
|
||||
sed -e "s/%VERSION%/$VERSION/g" \
|
||||
-e "s/%DATE%/$DATE/g" \
|
||||
<$1.in >$3
|
||||
else
|
||||
echo "$0: Fatal: don't know how to build '$1'" >&2
|
||||
exit 99
|
||||
fi
|
||||
8
docs/cookbook/defaults/include/version.h.in
Normal file
8
docs/cookbook/defaults/include/version.h.in
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// C/C++ header file identifying the current version
|
||||
#ifndef __VERSION_H
|
||||
#define __VERSION_H
|
||||
|
||||
#define VERSION "%VERSION%"
|
||||
#define DATE "%DATE%"
|
||||
|
||||
#endif // __VERSION_H
|
||||
322
docs/cookbook/defaults/index.md
Normal file
322
docs/cookbook/defaults/index.md
Normal file
|
|
@ -0,0 +1,322 @@
|
|||
### Building a text preprocessor
|
||||
|
||||
Let's say we want to process a bunch of files, each in the same way. An
|
||||
easy example is a text preprocessor: we'll look through the text and replace
|
||||
variables of the form %VARNAME% with real content. In our example, we'll
|
||||
use variable %VERSION%, representing a version number, and %DATE%, with
|
||||
today's date.
|
||||
|
||||
To play with this code on your own machine, get the [redo
|
||||
source code](https://github.com/apenwarr/redo) and look in the
|
||||
`docs/cookbook/defaults/` directory.
|
||||
|
||||
### Input files
|
||||
|
||||
Let's create some input files we can use. The input to our
|
||||
preprocessor will have file extension `.in`. The output can be in various
|
||||
formats.
|
||||
<pre><code src='test.txt.in'></code></pre>
|
||||
<pre><code lang='c' src='include/version.h.in'></code></pre>
|
||||
<pre><code lang='python' src='version.py.in'></code></pre>
|
||||
|
||||
For fun, let's also make a python test program that calls the newly
|
||||
generated version.py:
|
||||
<pre><code lang='python' src='test.py'></code></pre>
|
||||
|
||||
Finally, we need to provide values for the variables. Let's put each one
|
||||
in its own file, named `version` and `date`, respectively.
|
||||
|
||||
<pre><b style='text-align: center; display: block'>version</b
|
||||
><code>1.0</code></pre>
|
||||
|
||||
<pre><b style='text-align: center; display: block'>date</b
|
||||
><code>1970-01-01</code></pre>
|
||||
|
||||
### default.do files
|
||||
|
||||
Now we want to teach redo how to do our substitutions: how to, in general,
|
||||
generate file X from file X.in.
|
||||
|
||||
We could write a separate .do file for every output file X. For example, we
|
||||
might make `test.txt.do` and `version.py.do`. But that gets tedious. To
|
||||
make it easier, if there is no specific `X.do` for a target named X, redo
|
||||
will try using `default.do` instead. Let's write a default.do for our text
|
||||
preprocessor.
|
||||
<pre><code lang='sh' src='default.do'></code></pre>
|
||||
|
||||
If `default.do` is asked to build `X`, and there exists a file named `X.in`, we
|
||||
use `sed` to do our variable substitutions. In that case, `default.do` uses
|
||||
redo-ifchange to depend on `X.in`, `version`, and `date`. If a file named
|
||||
`X.in` does *not* exist, then we don't know what to do, so we give an error.
|
||||
|
||||
On the other hand, if we try to generate a file that *already* exists, like
|
||||
`test.py`, redo does not call default.do at all. redo only tries to create
|
||||
files that don't exist, or that were previously generated by redo. This
|
||||
stops redo from accidentally overwriting your work.
|
||||
```shell
|
||||
$ redo test.txt
|
||||
redo test.txt
|
||||
|
||||
$ cat test.txt
|
||||
This is the documentation for MyProgram version
|
||||
1.0. It was generated on 1970-01-01.
|
||||
|
||||
$ redo chicken
|
||||
redo chicken
|
||||
default.do: Fatal: don't know how to build 'chicken'
|
||||
redo chicken (exit 99)
|
||||
|
||||
$ redo version.py
|
||||
redo version.py
|
||||
|
||||
# test.py was created by us, so it's a "source" file.
|
||||
# redo does *not* call default.do to replace it.
|
||||
$ redo test.py
|
||||
redo: test.py: exists and not marked as generated; not redoing.
|
||||
|
||||
$ python test.py
|
||||
Version '1.0' has build date '1970-01-01'
|
||||
```
|
||||
|
||||
Nice!
|
||||
|
||||
While we're here, let's make an `all.do` so that we don't have to tell redo
|
||||
exactly which files to rebuild, every single time.
|
||||
<pre><code lang='sh' src='all.do'></code></pre>
|
||||
|
||||
Results:
|
||||
```shell
|
||||
$ redo
|
||||
redo all
|
||||
redo test.txt
|
||||
redo version.py
|
||||
redo include/version.h
|
||||
|
||||
# input files didn't change, so nothing to rebuild
|
||||
$ redo
|
||||
redo all
|
||||
|
||||
$ touch test.txt.in
|
||||
|
||||
$ redo
|
||||
redo all
|
||||
redo test.txt
|
||||
```
|
||||
|
||||
|
||||
### Auto-generating the version and date (redo-always and redo-stamp)
|
||||
|
||||
Of course, in a real project, we won't want to hardcode the version number
|
||||
and date into a file. Ideally, we can get the version number from a version
|
||||
control system, like git, and we can use today's date.
|
||||
|
||||
To make that happen, we can replace the static `version` and `date` files with
|
||||
`version.do` and `date.do`. `default.do` already uses redo-ifchange to
|
||||
depend on `version` and `date`, so redo will create them as needed, and
|
||||
if they change, redo will rebuild all the targets that depend on them.
|
||||
|
||||
However, the `version` and `date` files are special: they depend on the
|
||||
environment outside redo itself. That is, there's no way to declare a
|
||||
dependency on the current date. We might generate the `date` file once, but
|
||||
tomorrow, there's no way for redo to know that its value should change.
|
||||
|
||||
To handle this situation, redo has the `redo-always` command. If we run
|
||||
redo-always from a .do file, it means every time someone depends on that
|
||||
target, it will be considered out-of-date and need to be rebuilt. The
|
||||
result looks like this:
|
||||
```shell
|
||||
$ redo
|
||||
redo all
|
||||
redo test.txt
|
||||
redo version
|
||||
redo date
|
||||
redo version.py
|
||||
redo include/version.h
|
||||
|
||||
# version.do and date.do are redo-always, so
|
||||
# everything depending on them needs to rebuild
|
||||
# every time.
|
||||
$ redo
|
||||
redo all
|
||||
redo test.txt
|
||||
redo version
|
||||
redo date
|
||||
redo version.py
|
||||
redo include/version.h
|
||||
```
|
||||
|
||||
Of course, for many uses, that's overcompensating: the version number and
|
||||
date don't change *that* often, so we might end up doing a lot of
|
||||
unnecessary work on every build. To solve that, there's `redo-stamp`.
|
||||
redo-stamp does the opposite of redo-always: while redo-always makes things
|
||||
build *more* often, redo-stamp makes things build *less* often.
|
||||
Specifically, it lets a .do file provide a "stamp value" for its output; if
|
||||
that stamp value is the same as before, then the target should be considered
|
||||
unchanged after all.
|
||||
|
||||
The most common stamp value is just the content itself. Since in redo, we
|
||||
write the content to $3, we can also read it back from $3:
|
||||
<pre><code lang='sh' src='version.do'></code></pre>
|
||||
<pre><code lang='sh' src='date.do'></code></pre>
|
||||
|
||||
And the final result is what we want. Although `version` and `date` are
|
||||
generated every time, the targets which depend on them are not:
|
||||
```shell
|
||||
$ redo clean
|
||||
redo clean
|
||||
|
||||
# version and date are generated just once per run,
|
||||
# the first time they are used.
|
||||
$ redo
|
||||
redo all
|
||||
redo test.txt
|
||||
redo version
|
||||
redo date
|
||||
redo version.py
|
||||
redo include/version.h
|
||||
|
||||
# Here, (test.txt) means redo is considering building
|
||||
# test.txt, but can't decide yet. In order to decide,
|
||||
# it needs to first build date and version. After
|
||||
# that, it decides not to build test.txt after all.
|
||||
$ redo
|
||||
redo all
|
||||
redo (test.txt)
|
||||
redo date
|
||||
redo version
|
||||
```
|
||||
|
||||
|
||||
### Temporary overrides
|
||||
|
||||
Sometimes you want to override a file even if it *is* a target (ie. it has
|
||||
previously been built by redo and has a valid .do file associated with it).
|
||||
In our example, maybe you want to hardcode the version number because you're
|
||||
building a release. This is easy: redo notices whenever you overwrite a
|
||||
file from outside redo, and will avoid replacing that file until you
|
||||
subsequently delete it:
|
||||
```shell
|
||||
$ echo "1.0" >version
|
||||
|
||||
$ redo
|
||||
redo all
|
||||
redo (test.txt)
|
||||
redo date
|
||||
redo: version - you modified it; skipping
|
||||
redo test.txt
|
||||
redo version.py
|
||||
redo include/version.h
|
||||
|
||||
$ redo
|
||||
redo all
|
||||
redo (test.txt)
|
||||
redo date
|
||||
redo: version - you modified it; skipping
|
||||
|
||||
$ rm version
|
||||
|
||||
$ redo
|
||||
redo all
|
||||
redo (test.txt)
|
||||
redo date
|
||||
redo version
|
||||
redo test.txt
|
||||
redo version.py
|
||||
redo include/version.h
|
||||
```
|
||||
|
||||
### default.do, subdirectories, and redo-whichdo
|
||||
|
||||
There's one more thing we should mention, which is the interaction of
|
||||
default.do with files in subdirectories. Notice that we are building
|
||||
`include/version.h` in our example:
|
||||
```shell
|
||||
$ redo include/version.h
|
||||
redo include/version.h
|
||||
redo version
|
||||
redo date
|
||||
|
||||
$ cat include/version.h
|
||||
// C/C++ header file identifying the current version
|
||||
#ifndef __VERSION_H
|
||||
#define __VERSION_H
|
||||
|
||||
#define VERSION "redo-0.31-3-g974eb9f"
|
||||
#define DATE "2018-11-26"
|
||||
|
||||
#endif // __VERSION_H
|
||||
```
|
||||
|
||||
redo works differently from the `make` command when you ask it to build
|
||||
files in subdirectories. In make's case, it always looks for a `Makefile`
|
||||
in the *current* directory, and uses that for all build instructions. So
|
||||
`make include/version.h` and `cd include && make version.h` are two
|
||||
different things; the first uses `Makefile`, and the second uses
|
||||
`include/Makefile` (or crashes if the latter does not exist).
|
||||
|
||||
redo, on the other hand, always uses the same formula to find a .do file for
|
||||
a particular target. For a file named X, that formula is as follows:
|
||||
|
||||
- first, try X.do
|
||||
- then try default.do
|
||||
- then try ../default.do
|
||||
- then try ../../default.do
|
||||
- ...and so on...
|
||||
|
||||
(Note: for targets with an extension, like X.o, redo actually tries even
|
||||
more .do files, like `default.o.do` and `../default.o.do`. For precise
|
||||
details, read the [redo man page](../../redo).)
|
||||
|
||||
You can see which .do files redo considers for a given target by using the
|
||||
`redo-whichdo` command. If redo-whichdo returns successfully, the last name
|
||||
in the list is the .do file it finally decided to use.
|
||||
```shell
|
||||
$ redo-whichdo include/version.h
|
||||
include/version.h.do
|
||||
include/default.h.do
|
||||
include/default.do
|
||||
default.h.do
|
||||
default.do
|
||||
```
|
||||
|
||||
|
||||
### Redo always runs in the .do file's directory
|
||||
|
||||
To ensure consistency, redo always changes the current directory to
|
||||
the directory *containing the selected .do file* (**not** the directory
|
||||
containing the target, if they are different). As a result,
|
||||
`redo include/version.h` and `cd include && redo version.h` always have
|
||||
exactly the same effect:
|
||||
```shell
|
||||
$ redo include/version.h
|
||||
redo include/version.h
|
||||
redo version
|
||||
redo date
|
||||
|
||||
$ (cd include && redo version.h)
|
||||
redo version.h
|
||||
redo ../version
|
||||
redo ../date
|
||||
```
|
||||
|
||||
(redo's display is slightly different between the two: it always shows the
|
||||
files it's building relative to the $PWD at the time you started redo.)
|
||||
|
||||
This feature is critical to redo's recursive nature; it's the reason that
|
||||
essays like [Recursive Make Considered Harmful](http://aegis.sourceforge.net/auug97.pdf)
|
||||
don't apply to redo. Any redo target, anywhere in your source tree, can
|
||||
use redo-ifchange to depend on any of your other targets, and the dependency
|
||||
will work right.
|
||||
|
||||
Why does redo change to the directory containing the .do file, instead of
|
||||
the directory containing the target? Because usually, the .do file needs to
|
||||
refer to other dependencies, and it's easier to always express those
|
||||
dependencies without adjusting any paths. In our text preprocessor example,
|
||||
`default.do` does `redo-ifchange version date`; this wouldn't work properly
|
||||
if it were running from the `include/` directory, because there are no files
|
||||
named `version` and `date` in there.
|
||||
|
||||
Similarly, when compiling C programs, there are
|
||||
usually compiler options like `-I../mylib/include`. If we're compiling
|
||||
`foo.o` and `mydir/bar.o`, we would like `-I../mylib/include` to have the
|
||||
same meaning in both cases.
|
||||
6
docs/cookbook/defaults/test.py
Normal file
6
docs/cookbook/defaults/test.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
"""Test program for auto-generated version.py"""
|
||||
import version
|
||||
|
||||
print('Version %r has build date %r'
|
||||
% (version.VERSION, version.DATE))
|
||||
2
docs/cookbook/defaults/test.txt.in
Normal file
2
docs/cookbook/defaults/test.txt.in
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
This is the documentation for MyProgram version
|
||||
%VERSION%. It was generated on %DATE%.
|
||||
7
docs/cookbook/defaults/version.do
Normal file
7
docs/cookbook/defaults/version.do
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Try to get a version number from git, if possible.
|
||||
if ! git describe >$3; then
|
||||
echo "$0: Falling back to static version." >&2
|
||||
echo 'UNKNOWN' >$3
|
||||
fi
|
||||
redo-always
|
||||
redo-stamp <$3
|
||||
3
docs/cookbook/defaults/version.py.in
Normal file
3
docs/cookbook/defaults/version.py.in
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# python module identifying the current version
|
||||
VERSION='%VERSION%'
|
||||
DATE='%DATE%'
|
||||
3
docs/cookbook/hello/.gitignore
vendored
Normal file
3
docs/cookbook/hello/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
hello
|
||||
*~
|
||||
.*~
|
||||
1
docs/cookbook/hello/all.do
Normal file
1
docs/cookbook/hello/all.do
Normal file
|
|
@ -0,0 +1 @@
|
|||
redo-ifchange hello
|
||||
1
docs/cookbook/hello/clean.do
Normal file
1
docs/cookbook/hello/clean.do
Normal file
|
|
@ -0,0 +1 @@
|
|||
rm -f hello *~ .*~
|
||||
6
docs/cookbook/hello/hello.c
Normal file
6
docs/cookbook/hello/hello.c
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
printf("Hello, world!\n");
|
||||
return 0;
|
||||
}
|
||||
16
docs/cookbook/hello/hello.do
Normal file
16
docs/cookbook/hello/hello.do
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# If hello.c changes, this script needs to be
|
||||
# re-run.
|
||||
redo-ifchange hello.c
|
||||
|
||||
# Compile hello.c into the 'hello' binary.
|
||||
#
|
||||
# $3 is the redo variable that represents the
|
||||
# output filename. We want to build a file
|
||||
# called "hello", but if we write that directly,
|
||||
# then an interruption could result in a
|
||||
# partially-written file. Instead, write it to
|
||||
# $3, and redo will move our output into its
|
||||
# final location, only if this script completes
|
||||
# successfully.
|
||||
#
|
||||
cc -o $3 hello.c -Wall
|
||||
142
docs/cookbook/hello/index.md
Normal file
142
docs/cookbook/hello/index.md
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
### Hello!
|
||||
|
||||
Let's start with Hello World: famously, the simplest project that does
|
||||
anything interesting. We'll write this one in C, but don't worry if
|
||||
you're not a C programmer. The focus isn't the C code itself, just to
|
||||
compile it.
|
||||
|
||||
To play with the code on your own machine, get the [redo
|
||||
source code](https://github.com/apenwarr/redo) and look in the
|
||||
`docs/cookbook/hello/` directory.
|
||||
|
||||
### Compiling the code
|
||||
|
||||
First, let's create a source file that we want to compile:
|
||||
<pre><code lang='c' src='hello.c'></code></pre>
|
||||
|
||||
Now we need a .do file to tell redo how to compile it:
|
||||
<pre><code lang='sh' src='hello.do'></code></pre>
|
||||
|
||||
With those files in place, we can build and run the program:
|
||||
```shell
|
||||
$ redo hello
|
||||
redo hello
|
||||
|
||||
$ ./hello
|
||||
Hello, world!
|
||||
```
|
||||
|
||||
Use the `redo` command to forcibly re-run a specific rule (in this case, the
|
||||
compiler). Or, if you only want to recompile `hello` when its input
|
||||
files (dependencies) have changed, use `redo-ifchange`.
|
||||
```shell
|
||||
$ redo hello
|
||||
redo hello
|
||||
|
||||
# Rebuilds, whether we need it or not
|
||||
$ redo hello
|
||||
redo hello
|
||||
|
||||
# Does not rebuild because hello.c is unchanged
|
||||
$ redo-ifchange hello
|
||||
|
||||
$ touch hello.c
|
||||
|
||||
# Notices the change to hello.c
|
||||
$ redo-ifchange hello
|
||||
redo hello
|
||||
```
|
||||
|
||||
Usually we'll want to also provide an `all.do` file. `all` is the
|
||||
default redo target when you don't specify one.
|
||||
<pre><code lang='sh' src='all.do'></code></pre>
|
||||
|
||||
With that, now we can rebuild our project by just typing `redo`:
|
||||
```shell
|
||||
$ rm hello
|
||||
|
||||
# 'redo' runs all.do, which calls into hello.do.
|
||||
$ redo
|
||||
redo all
|
||||
redo hello
|
||||
|
||||
# Notice that this forcibly re-runs the 'all'
|
||||
# rule, but all.do calls redo-ifchange, so
|
||||
# hello itself is only recompiled if its
|
||||
# dependencies change.
|
||||
$ redo
|
||||
redo all
|
||||
|
||||
$ ./hello
|
||||
Hello, world!
|
||||
```
|
||||
|
||||
|
||||
### Debugging your .do scripts
|
||||
|
||||
If you want to see exactly which commands are being run for each step,
|
||||
you can use redo's `-x` and `-v` options, which work similarly to
|
||||
`sh -x` and `sh -v`.
|
||||
|
||||
```shell
|
||||
$ rm hello
|
||||
|
||||
$ redo -x
|
||||
redo all
|
||||
* sh -ex all.do all all all.redo2.tmp
|
||||
+ redo-ifchange hello
|
||||
|
||||
redo hello
|
||||
* sh -ex hello.do hello hello hello.redo2.tmp
|
||||
+ redo-ifchange hello.c
|
||||
+ cc -o hello.redo2.tmp hello.c -Wall
|
||||
redo hello (done)
|
||||
|
||||
redo all (done)
|
||||
```
|
||||
|
||||
|
||||
### Running integration tests
|
||||
|
||||
What about tests? We can, of course, compile a C program that has some
|
||||
unit tests. But since our program isn't very complicated, let's write
|
||||
a shell "integration test" (also known as a "black box" test) to make
|
||||
sure it works as expected, without depending on implementation details:
|
||||
<pre><code lang='sh' src='test.do'></code></pre>
|
||||
|
||||
Even if we rewrote our hello world program in python, javascript, or
|
||||
ruby, that integration test would still be useful.
|
||||
|
||||
|
||||
### Housekeeping
|
||||
|
||||
Traditionally, it's considered polite to include a `clean` rule that
|
||||
restores your project to pristine status, so people can rebuild from
|
||||
scratch:
|
||||
<pre><code lang='sh' src='clean.do'></code></pre>
|
||||
|
||||
Some people like to include a `.gitignore` file so that git won't pester
|
||||
you about files that would be cleaned up by `clean.do` anyway. Let's add
|
||||
one:
|
||||
<pre><b align=center style="display: block">.gitignore</b><code>
|
||||
hello
|
||||
*~
|
||||
.*~
|
||||
</code></pre>
|
||||
|
||||
Congratulations! That's all it takes to make your first redo project.
|
||||
|
||||
Here's what it looks like when we're done:
|
||||
```shell
|
||||
$ ls
|
||||
all.do clean.do hello.c hello.do test.do
|
||||
```
|
||||
|
||||
Some people think this looks a little cluttered with .do files. But
|
||||
notice one very useful feature: you can see, at a glance, exactly which
|
||||
operations are possible in your project. You can redo all, clean,
|
||||
hello, or test. Since most people downloading your project will just
|
||||
want to build it, it's helpful to have the available actions so
|
||||
prominently displayed. And if they have a problem with one of the
|
||||
steps, it's very obvious which file contains the script that's causing
|
||||
the problem.
|
||||
12
docs/cookbook/hello/test.do
Normal file
12
docs/cookbook/hello/test.do
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# Make sure everything has been built before we start
|
||||
redo-ifchange all
|
||||
|
||||
# Ensure that the hello program, when run, says
|
||||
# hello like we expect.
|
||||
if ./hello | grep -i 'hello' >/dev/null; then
|
||||
echo "success" >&2
|
||||
exit 0
|
||||
else
|
||||
echo "missing 'hello' message!" >&2
|
||||
exit 1
|
||||
fi
|
||||
7
docs/cookbook/latex/.gitignore
vendored
Normal file
7
docs/cookbook/latex/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
*.eps
|
||||
*.dvi
|
||||
*.ps
|
||||
*.pdf
|
||||
*.tmp
|
||||
*~
|
||||
.*~
|
||||
8
docs/cookbook/latex/all.do
Normal file
8
docs/cookbook/latex/all.do
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
for d in latex dvips dvipdf Rscript; do
|
||||
if ! type "$d" >/dev/null 2>/dev/null; then
|
||||
echo "$0: skipping: $d not installed." >&2
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
|
||||
redo-ifchange paper.pdf paper.ps
|
||||
2
docs/cookbook/latex/clean.do
Normal file
2
docs/cookbook/latex/clean.do
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
rm -f *.eps *.dvi *.ps *.pdf *~ .*~
|
||||
rm -rf *.tmp
|
||||
2
docs/cookbook/latex/default.dvi.do
Normal file
2
docs/cookbook/latex/default.dvi.do
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
redo-ifchange "$2.runtex"
|
||||
ln "$2.tmp/$2.dvi" "$3"
|
||||
3
docs/cookbook/latex/default.pdf.do
Normal file
3
docs/cookbook/latex/default.pdf.do
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
exec >&2
|
||||
redo-ifchange "$2.dvi"
|
||||
dvipdf "$2.dvi" "$3"
|
||||
3
docs/cookbook/latex/default.ps.do
Normal file
3
docs/cookbook/latex/default.ps.do
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
exec >&2
|
||||
redo-ifchange "$2.dvi"
|
||||
dvips -o "$3" "$2.dvi"
|
||||
58
docs/cookbook/latex/default.runtex.do
Normal file
58
docs/cookbook/latex/default.runtex.do
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
# latex produces log output on stdout, which is
|
||||
# not really correct. Send it to stderr instead.
|
||||
exec >&2
|
||||
|
||||
# We depend on both the .latex file and its .deps
|
||||
# file (which lists additional dependencies)
|
||||
redo-ifchange "$2.latex" "$2.deps"
|
||||
|
||||
# Next, we have to depend on each dependency in
|
||||
# the .deps file.
|
||||
cat "$2.deps" | xargs redo-ifchange
|
||||
|
||||
tmp="$2.tmp"
|
||||
rm -rf "$tmp"
|
||||
mkdir -p "$tmp"
|
||||
|
||||
# latex generates eg. the table of contents by
|
||||
# using a list of references ($2.aux) generated
|
||||
# during its run. The first time, the table of
|
||||
# contents is empty, so we have to run again.
|
||||
# But then the table of contents is non-empty,
|
||||
# which might cause page numbers to change, and
|
||||
# so on. So we have to keep re-running until it
|
||||
# finally stops changing.
|
||||
touch "$tmp/$2.aux.old"
|
||||
ok=
|
||||
for i in $(seq 5); do
|
||||
latex --halt-on-error \
|
||||
--output-directory="$tmp" \
|
||||
--recorder \
|
||||
"$2.latex" </dev/null
|
||||
if diff "$tmp/$2.aux.old" \
|
||||
"$tmp/$2.aux" >/dev/null; then
|
||||
# .aux file converged, so we're done
|
||||
ok=1
|
||||
break
|
||||
fi
|
||||
echo
|
||||
echo "$0: $2.aux changed: try again (try #$i)"
|
||||
echo
|
||||
cp "$tmp/$2.aux" "$tmp/$2.aux.old"
|
||||
done
|
||||
|
||||
if [ "$ok" = "" ]; then
|
||||
echo "$0: fatal: $2.aux did not converge!"
|
||||
exit 10
|
||||
fi
|
||||
|
||||
# If the newly produced .dvi disappears, we need
|
||||
# to redo.
|
||||
redo-ifchange "$tmp/$2.dvi"
|
||||
|
||||
# With --recorder, latex produces a list of files
|
||||
# it used during its run. Let's depend on all of
|
||||
# them, so if they ever change, we'll redo.
|
||||
grep ^INPUT "$tmp/$2.fls" |
|
||||
cut -d' ' -f2 |
|
||||
xargs redo-ifchange
|
||||
1
docs/cookbook/latex/discovery.txt
Normal file
1
docs/cookbook/latex/discovery.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
It seems that \(E = m c^2\).
|
||||
252
docs/cookbook/latex/index.md
Normal file
252
docs/cookbook/latex/index.md
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
### A LaTeX typesetting example
|
||||
|
||||
[LaTeX](https://www.latex-project.org/) is a typesetting system that's
|
||||
especially popular in academia. Among other things, it lets you produce
|
||||
postscript and pdf files from a set of (mostly text) input files.
|
||||
|
||||
LaTeX documents often include images and charts. In our example, we'll show
|
||||
how to auto-generate a chart for inclusion using an [R script with
|
||||
ggplot2](https://ggplot2.tidyverse.org/).
|
||||
|
||||
To play with this code on your own machine, get the [redo
|
||||
source code](https://github.com/apenwarr/redo) and look in the
|
||||
`docs/cookbook/latex/` directory.
|
||||
|
||||
|
||||
### Generating a plot from an R script
|
||||
|
||||
First, let's tell redo how to generate our chart. We'll use
|
||||
the R language, and ask it to plot some of its sample data (the mpg, "miles
|
||||
per gallon" data set) and save it to an eps (encapsulated postscript) file.
|
||||
eps files are usually a good format for LaTeX embedded images, because they
|
||||
scale to any printer or display resolution.
|
||||
|
||||
First, let's make an R script that generates a plot:
|
||||
<pre><code lang='r' src='mpg.R'></code></pre>
|
||||
|
||||
And then a .do file to tie that into redo:
|
||||
<pre><code lang='sh' src='mpg.eps.do'></code></pre>
|
||||
|
||||
We can build and view the image:
|
||||
```shell
|
||||
$ redo mpg.eps
|
||||
redo mpg.eps
|
||||
|
||||
# View the file on Linux
|
||||
$ evince mpg.eps
|
||||
|
||||
# View the file on MacOS
|
||||
$ open mpg.eps
|
||||
```
|
||||
|
||||
|
||||
### Running the LaTeX processor
|
||||
|
||||
Here's the first draft of our very important scientific paper:
|
||||
<pre><code lang='tex' src='paper.latex'></code></pre>
|
||||
|
||||
Notice how it refers to the chart from above, `mpg.eps`, and a text file,
|
||||
`discovery.txt`. Let's create the latter as a static file.
|
||||
<pre><code lang='tex' src='discovery.txt'></code></pre>
|
||||
|
||||
With all the parts of our document in places, we can now compile it directly
|
||||
using `pdflatex`:
|
||||
```shell
|
||||
$ pdflatex paper.latex
|
||||
This is pdfTeX, Version 3.14159265-2.6-1.40.17 (TeX Live 2016/Debian) (preloaded format=pdflatex)
|
||||
restricted \write18 enabled.
|
||||
entering extended mode
|
||||
...[a lot of unnecessary diagnostic messages]...
|
||||
Output written on paper.pdf (2 pages, 68257 bytes).
|
||||
Transcript written on paper.log.
|
||||
```
|
||||
|
||||
But this has a few problems. First of all, it doesn't understand
|
||||
dependencies; if `mpg.R` changes, it won't know to rebuild `mpg.eps`.
|
||||
Secondly, the TeX/LaTeX toolchain has an idiosyncracy that means you might
|
||||
have to rebuild your document more than once. In our example, we generate a
|
||||
table of contents, but it ends up getting generated *before* processing the
|
||||
rest of the content in the document, so it's initially blank. As it
|
||||
continues, LaTeX produces a file called `paper.aux` with a list of the
|
||||
references needed by the table of contents, and their page numbers. If we
|
||||
run LaTeX over again, it'll use that to build a proper of table of contents.
|
||||
|
||||
Of course, life is not necessarily so easy. Once the table of contents
|
||||
isn't blank, it might start to push content onto the next page. This will
|
||||
change all the page numbers! So we'd have to do it one more time. And that
|
||||
might lead to even more subtle problems, like a reference to page 99
|
||||
changing to page 100, which pushes a word onto the next page, which changes
|
||||
some other page number, and so on. Thus, we need a script that will keep
|
||||
looping, re-running LaTeX until `paper.aux` stabilizes.
|
||||
|
||||
The whole script we'll use is below. Instead of running `pdflatex`
|
||||
directly, we'll use the regular `latex` command, which produces a .dvi
|
||||
(DeVice Independent) intermediate file which we can later turn into a pdf or
|
||||
ps file.
|
||||
|
||||
LaTeX produces a bunch of clutter files (like `paper.aux`) that can be used
|
||||
in future runs, but which also make its execution nondeterministic. To
|
||||
avoid that problem, we tell it to use a temporary `--output-directory` that
|
||||
we delete and recreate before each build (although we might need to run
|
||||
`latex` multiple times in one build, to get `paper.aux` to converge).
|
||||
<pre><code lang='sh' src='default.runtex.do'></code></pre>
|
||||
|
||||
|
||||
### Virtual targets, side effects, and multiple outputs
|
||||
|
||||
Why did we call our script `default.runtex.do`? Why not `default.pdf.do` or
|
||||
`default.dvi.do`, depending what kind of file we ask LaTeX to produce?
|
||||
|
||||
The problem is that the `latex` command actually produces several
|
||||
files in that temporary directory, and we might want to keep them around.
|
||||
If we name our .do file after only *one* of those outputs, things get messy.
|
||||
|
||||
The biggest problem is that redo requires a .do file to write its output to
|
||||
$3 (or stdout), so that it can guarantee the output gets replaced
|
||||
atomically. When there is more than one output, at most one file can
|
||||
be sent to $3; how do you choose which one? Even worse, some programs don't
|
||||
even have the ability to choose the output filename; for an input of
|
||||
`paper.latex`, the `latex` command just writes a bunch of files named
|
||||
`paper.*` directly. You can't ask it to put just one of them in $3.
|
||||
|
||||
The easiest way to handle this situation in redo is to use a "virtual
|
||||
target", which is a target name that doesn't actually get created has a file,
|
||||
and has only side effects. You've seen these before: when we use `all.do`
|
||||
or `clean.do`, we don't expect to produce a file named `all` or `clean`. We
|
||||
expect redo to run a collection of other commands. In `make`, these are
|
||||
sometimes called ".PHONY rules" because of the way they are declared in a
|
||||
`Makefile`. But the rules aren't phony, they really are executed; they just
|
||||
don't produce output. So in redo we call them "virtual."
|
||||
|
||||
When we `redo paper.runtex`, it builds our virtual target. There is no
|
||||
`paper.runtex` file or directory generated. But as a side effect, a
|
||||
directory named `paper.tmp` is created.
|
||||
|
||||
(Side note: it's tempting to name the directory the same as the target. So
|
||||
we could have a `paper.runtex` directory instead of `paper.tmp`. This is
|
||||
not inherently a bad idea, but currently redo behaviour is undefined if you
|
||||
redo-ifchange a directory. Directories are weird. If one file in that
|
||||
directory disappears, does that mean you "modified" the output by hand?
|
||||
What if two redo targets modify the same directory? Should we require
|
||||
scripts to only atomically replace an entire output directory via $3? And
|
||||
so on. We might carefully define this behaviour eventually, but for now,
|
||||
it's better to use a separate directory name and avoid the undefined
|
||||
behaviour.)
|
||||
|
||||
|
||||
### Depending on side effects produced by virtual targets
|
||||
|
||||
Next, we want to produce .pdf and .ps files from the collection of files
|
||||
produced by the `latex` command, particularly `paper.tmp/paper.dvi`. To do
|
||||
that, we have to bring our files back from the "virtual target" world into
|
||||
the real world.
|
||||
|
||||
Depending on virtual targets is easy; we'll just
|
||||
`redo-ifchange paper.runtex`. Then we want to materialize `paper.dvi` from
|
||||
the temporary files in `paper.tmp/paper.dvi`, which we can do with an
|
||||
efficient [hardlink](https://en.wikipedia.org/wiki/Hard_link) (rather than
|
||||
making an unnecessary copy), like this:
|
||||
<pre><code lang='sh' src='default.dvi.do'></code></pre>
|
||||
|
||||
Notice that we *don't* do `redo-ifchange paper.tmp/paper.dvi`. That's
|
||||
because redo has no knowledge of that file. If you ask redo to build that
|
||||
file for you, it doesn't know how to do it. You have to ask for
|
||||
`paper.runtex`, which you know - but redo doesn't know - will produce the
|
||||
input file you want. Then you can safely use it.
|
||||
|
||||
Once we have a .do file that produces the "real" (non-virtual,
|
||||
non-side-effect) `paper.dvi` file, however, it's safe to depend directly on
|
||||
it. Let's use that to produce our .ps and .pdf outputs:
|
||||
<pre><code lang='sh' src='default.ps.do'></code></pre>
|
||||
<pre><code lang='sh' src='default.pdf.do'></code></pre>
|
||||
|
||||
(As above, we include `exec >&2` lines because LaTeX tools incorrectly write
|
||||
their log messages to stdout. We need to redirect it all to stderr. That
|
||||
way [redo-log](../../redo-log) can handle all the log output appropriately.)
|
||||
|
||||
|
||||
### Explicit dependencies
|
||||
|
||||
We've made a generalized script, `default.runtex.do`, that can compile any
|
||||
.latex file and produce a .tmp directory with its output. But that's not
|
||||
quite enough: different .latex files might have extra dependencies that need
|
||||
to exist *before* the compilation can continue. In our case, we need the
|
||||
auto-generated `mpg.eps` that we discussed above.
|
||||
|
||||
To make that work, `default.runtex.do` looks for a .deps file with the same
|
||||
name as the .latex file being processed. It contains just a list of extra
|
||||
dependencies that need to be built. Here's ours:
|
||||
<pre><code src='paper.deps'></code></pre>
|
||||
|
||||
You can use this same ".deps" technique in various different places in redo.
|
||||
For example, you could have a default.do that can link a C program from any
|
||||
set of .o files. To specify the right set of .o files for target `X`,
|
||||
default.do might look in an `X.deps` or `X.list` file. If you later want to
|
||||
get even fancier, you could make an `X.deps.do` that programmatically
|
||||
generates the list of dependencies; for example, it might include one set of
|
||||
files on win32 platforms and a different set on unix platforms.
|
||||
|
||||
|
||||
### Autodependencies
|
||||
|
||||
Our `paper.latex` file actually includes two files: `mpg.eps`, which we
|
||||
explicitly depended upon above, and `discovery.txt`, which we didn't. The
|
||||
latter is a static source file, so we can let redo discover it
|
||||
automatically, based on the set of files that LaTeX opens while it runs.
|
||||
The `latex` command has a `--record` option to do this; it produces a file
|
||||
called `paper.tmp/paper.fls` (.fls is short for "File LiSt").
|
||||
|
||||
One of redo's best features is that you can declare dependencies *after*
|
||||
you've done your build steps, when you have the best knowledge of which
|
||||
files were actually needed. That's why in `default.runtex.do`, we parse the
|
||||
.fls file and then redo-ifchange on its contents right at the end.
|
||||
|
||||
(This brings up a rather subtle point about how redo works. When you run
|
||||
redo-ifchange, redo adds to the list of files which, if they change, mean
|
||||
your target needs to be rebuilt. But unlike make, redo will not actually
|
||||
rebuild those files merely because they're listed as a dependency; it just
|
||||
knows to rebuild your target, which means to run your .do file, which will
|
||||
run redo-ifchange *again* if it still needs those input files to be fresh.
|
||||
|
||||
This avoids an annoying problem in `make` where you can teach it about
|
||||
which .h files your C program depended on last time, but if you change
|
||||
A.c to no longer include X.h, and then delete X.h, make might complain
|
||||
that X.h is missing, because A.c depended on it *last time*. redo will
|
||||
simply notice that since X.h is missing, A.c needs to be recompiled, and let
|
||||
your compilation .do script report an error, or not.)
|
||||
|
||||
Anyway, this feature catches not just our `discovery.txt` dependency, but
|
||||
also the implicit dependencies on various LaTeX template and font files, and
|
||||
so on. If any of those change, our LaTeX file needs to be rebuilt.
|
||||
```shell
|
||||
$ redo --no-detail paper.pdf
|
||||
redo paper.pdf
|
||||
redo paper.dvi
|
||||
redo paper.runtex
|
||||
redo mpg.eps
|
||||
|
||||
$ redo --no-detail paper.pdf
|
||||
redo paper.pdf
|
||||
|
||||
$ touch discovery.txt
|
||||
|
||||
$ redo --no-detail paper.pdf
|
||||
redo paper.pdf
|
||||
redo paper.dvi
|
||||
redo paper.runtex
|
||||
|
||||
$ redo --no-detail paper.pdf
|
||||
redo paper.pdf
|
||||
```
|
||||
|
||||
|
||||
### Housekeeping
|
||||
|
||||
As usual, to polish up our project, let's create an `all.do` and
|
||||
`clean.do`.
|
||||
|
||||
Because this project is included in the redo source and we don't want redo
|
||||
to fail to build just because you don't have LaTeX or R installed, we'll
|
||||
have `all.do` quit politely if the necessary tools are missing.
|
||||
<pre><code lang='sh' src='all.do'></code></pre>
|
||||
<pre><code lang='sh' src='clean.do'></code></pre>
|
||||
4
docs/cookbook/latex/mpg.R
Normal file
4
docs/cookbook/latex/mpg.R
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
library(ggplot2)
|
||||
|
||||
qplot(mpg, wt, data = mtcars) + facet_wrap(~cyl) + theme_bw()
|
||||
ggsave("mpg.new.eps", width=4, height=2, units='in')
|
||||
7
docs/cookbook/latex/mpg.eps.do
Normal file
7
docs/cookbook/latex/mpg.eps.do
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
redo-ifchange mpg.R
|
||||
Rscript mpg.R >&2
|
||||
mv mpg.new.eps $3
|
||||
|
||||
# Some buggy ggplot2 versions produce this
|
||||
# junk file; throw it away.
|
||||
rm -f Rplots.pdf
|
||||
1
docs/cookbook/latex/paper.deps
Normal file
1
docs/cookbook/latex/paper.deps
Normal file
|
|
@ -0,0 +1 @@
|
|||
mpg.eps
|
||||
17
docs/cookbook/latex/paper.latex
Normal file
17
docs/cookbook/latex/paper.latex
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
\documentclass{article}
|
||||
\usepackage{graphicx}
|
||||
|
||||
\title{A very brief note on relativity}
|
||||
\author{The Redo Contributors}
|
||||
|
||||
\begin{document}
|
||||
\maketitle
|
||||
\tableofcontents
|
||||
|
||||
\newpage
|
||||
\section{Amazing Discovery}
|
||||
\input{discovery.txt}
|
||||
|
||||
\section{Irrelevant Chart}
|
||||
\includegraphics{mpg.eps}
|
||||
\end{document}
|
||||
2
docs/default.1.do
Normal file
2
docs/default.1.do
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
redo-ifchange md-to-man $2.md.tmp
|
||||
. ./md-to-man $1 $2 $3
|
||||
7
docs/default.md.tmp.do
Normal file
7
docs/default.md.tmp.do
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
redo-ifchange ../redo/version/vars $2.md
|
||||
. ../redo/version/vars
|
||||
cat - $2.md <<-EOF
|
||||
% $2(1) Redo $TAG
|
||||
% Avery Pennarun <apenwarr@gmail.com>
|
||||
% $DATE
|
||||
EOF
|
||||
3
docs/doc.list.do
Normal file
3
docs/doc.list.do
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
ls redo*.md t/*.md >$3
|
||||
redo-always
|
||||
redo-stamp <$3
|
||||
8
docs/extra_style.css
Normal file
8
docs/extra_style.css
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
p code {
|
||||
border: 0;
|
||||
padding: 0 2px 0 2px;
|
||||
font-size: 100%;
|
||||
white-space: nowrap;
|
||||
color: #a04040;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
25
docs/fetchcode.js
Normal file
25
docs/fetchcode.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
'use strict';
|
||||
|
||||
async function fetchCode(e, lang, src) {
|
||||
const resp = await fetch(src);
|
||||
let t = await resp.text();
|
||||
if (lang) t = hljs.highlight(lang, t).value;
|
||||
e.innerHTML = t;
|
||||
}
|
||||
|
||||
function fetchAndHighlightAll() {
|
||||
const el = document.getElementsByTagName('code');
|
||||
for (const e of el) {
|
||||
const src = e.getAttribute('src');
|
||||
if (!src) continue;
|
||||
const lang = e.getAttribute('lang');
|
||||
const title = document.createElement('b');
|
||||
title.innerText = src;
|
||||
title.style.textAlign = 'center';
|
||||
title.style.display = 'block';
|
||||
e.parentElement.insertBefore(title, e);
|
||||
fetchCode(e, lang, src);
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(fetchAndHighlightAll, 0);
|
||||
15
docs/git-export.do
Normal file
15
docs/git-export.do
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# update the local 'man' branch with pregenerated output files, for people
|
||||
# who don't have pandoc (and maybe to aid in google searches or something)
|
||||
redo-ifchange all
|
||||
git update-ref refs/heads/man origin/man '' 2>/dev/null || true
|
||||
|
||||
export GIT_INDEX_FILE=gitindex.tmp
|
||||
rm -f $GIT_INDEX_FILE
|
||||
git add -f *.1
|
||||
|
||||
MSG="Autogenerated man pages for $(git describe)"
|
||||
TREE=$(git write-tree --prefix=docs)
|
||||
git show-ref refs/heads/man >/dev/null && PARENT="-p refs/heads/man"
|
||||
COMMITID=$(echo "$MSG" | git commit-tree $TREE $PARENT)
|
||||
|
||||
git update-ref refs/heads/man $COMMITID
|
||||
8
docs/git-import.do
Normal file
8
docs/git-import.do
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
exec >&2
|
||||
if git show-ref refs/heads/man >/dev/null; then
|
||||
(cd .. && git archive man) | tar -xvf -
|
||||
else
|
||||
(cd .. && git archive origin/man) | tar -xvf -
|
||||
fi
|
||||
|
||||
|
||||
258
docs/index.md
Normal file
258
docs/index.md
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
# redo: a recursive, general-purpose build system
|
||||
|
||||
`redo` is a competitor to the long-lived, but sadly imperfect, `make`
|
||||
program. Unlike other such competitors, redo captures the essential
|
||||
simplicity and flexibility of make, while avoiding its flaws. It manages to
|
||||
do this while being simultaneously simpler than make, more flexible than
|
||||
make, and more powerful than make, and without sacrificing performance - a
|
||||
rare combination of features.
|
||||
|
||||
The original design for redo comes from Daniel J. Bernstein (creator of
|
||||
qmail and djbdns, among many other useful things). He posted some
|
||||
terse notes on his web site at one point (there is no date) with the
|
||||
unassuming title, "[Rebuilding target files when source files have
|
||||
changed](http://cr.yp.to/redo.html)." Those notes are enough information to
|
||||
understand how the system is supposed to work; unfortunately there's no code
|
||||
to go with it. I wrote this implementation of redo from scratch, based on
|
||||
that design.
|
||||
|
||||
After I found out about djb redo, I searched the Internet for any sign that
|
||||
other people had discovered what I had: a hidden, unimplemented gem of
|
||||
brilliant code design. I found only one interesting link: Alan Grosskurth,
|
||||
whose [Master's thesis at the University of Waterloo](http://grosskurth.ca/papers/mmath-thesis.pdf)
|
||||
was about top-down software rebuilding, that is, djb redo. He wrote his
|
||||
own (admittedly slow) implementation in about 250 lines of shell script,
|
||||
which gives an idea for how straightforward the system is.
|
||||
|
||||
My implementation of redo is called `redo` for the same reason that there
|
||||
are 75 different versions of `make` that are all called `make`. It's somehow
|
||||
easier that way.
|
||||
|
||||
I also provide an extremely minimal pure-POSIX-sh implementation, called
|
||||
`do`, in the `minimal/` directory of this repository.
|
||||
|
||||
(Want to discuss redo? See the bottom of this file for
|
||||
information about our mailing list.)
|
||||
|
||||
|
||||
# What's so special about redo?
|
||||
|
||||
The theory behind redo sounds too good to be true: it can do everything
|
||||
`make` can do, but the implementation is vastly simpler, the syntax is
|
||||
cleaner, and you have even more flexibility without resorting to ugly hacks.
|
||||
Also, you get all the speed of non-recursive `make` (only check dependencies
|
||||
once per run) combined with all the cleanliness of recursive `make` (you
|
||||
don't have code from one module stomping on code from another module).
|
||||
|
||||
(Disclaimer: my current implementation is not as fast as `make` for some
|
||||
things, because it's written in python. Eventually I'll rewrite it an C and
|
||||
it'll be very, very fast.)
|
||||
|
||||
The easiest way to show it is to jump into an example. Here's one for
|
||||
compiling a C++ program.
|
||||
|
||||
Create a file called default.o.do:
|
||||
|
||||
redo-ifchange $2.c
|
||||
gcc -MD -MF $2.d -c -o $3 $2.c
|
||||
read DEPS <$2.d
|
||||
redo-ifchange ${DEPS#*:}
|
||||
|
||||
Create a file called myprog.do:
|
||||
|
||||
DEPS="a.o b.o"
|
||||
redo-ifchange $DEPS
|
||||
gcc -o $3 $DEPS
|
||||
|
||||
Of course, you'll also have to create `a.c` and `b.c`, the C language
|
||||
source files that you want to build to create your application.
|
||||
|
||||
In a.c:
|
||||
|
||||
#include <stdio.h>
|
||||
#include "b.h"
|
||||
|
||||
int main() { printf(bstr); }
|
||||
|
||||
In b.h:
|
||||
|
||||
extern char *bstr;
|
||||
|
||||
In b.c:
|
||||
|
||||
char *bstr = "hello, world!\n";
|
||||
|
||||
Now you simply run:
|
||||
|
||||
$ redo myprog
|
||||
|
||||
And it says:
|
||||
|
||||
redo myprog
|
||||
redo a.o
|
||||
redo b.o
|
||||
|
||||
Now try this:
|
||||
|
||||
$ touch b.h
|
||||
$ redo myprog
|
||||
|
||||
Sure enough, it says:
|
||||
|
||||
redo myprog
|
||||
redo a.o
|
||||
|
||||
Did you catch the shell incantation in `default.o.do` where it generates
|
||||
the autodependencies? The filename `default.o.do` means "run this script to
|
||||
generate a .o file unless there's a more specific whatever.o.do script that
|
||||
applies."
|
||||
|
||||
The key thing to understand about redo is that declaring a dependency is just
|
||||
another shell command. The `redo-ifchange` command means, "build each of my
|
||||
arguments. If any of them or their dependencies ever change, then I need to
|
||||
run the *current script* over again."
|
||||
|
||||
Dependencies are tracked in a persistent `.redo` database so that redo can
|
||||
check them later. If a file needs to be rebuilt, it re-executes the
|
||||
`whatever.do` script and regenerates the dependencies. If a file doesn't
|
||||
need to be rebuilt, redo figures that out just using its persistent
|
||||
`.redo` database, without re-running the script. And it can do that check
|
||||
just once right at the start of your project build, which is really fast.
|
||||
|
||||
Best of all, as you can see in `default.o.do`, you can declare a dependency
|
||||
*after* building the program. In C, you get your best dependency
|
||||
information by trying to actually build, since that's how you find out which
|
||||
headers you need. redo is based on this simple insight: you don't
|
||||
actually care what the dependencies are *before* you build the target. If
|
||||
the target doesn't exist, you obviously need to build it.
|
||||
|
||||
Once you're building it anyway, the build script itself can calculate the
|
||||
dependency information however it wants; unlike in `make`, you don't need a
|
||||
special dependency syntax at all. You can even declare some of your
|
||||
dependencies after building, which makes C-style autodependencies much
|
||||
simpler.
|
||||
|
||||
redo therefore is a unique combination of imperative and declarative
|
||||
programming. The initial build is almost entirely imperative (running a
|
||||
series of scripts). As part of that, the scripts declare dependencies a few
|
||||
at a time, and redo assembles those into a larger data structure. Then, in
|
||||
the future, it uses that pre-declared data structure to decide what work
|
||||
needs to be redone.
|
||||
|
||||
(GNU make supports putting some of your dependencies in include files, and
|
||||
auto-reloading those include files if they change. But this is very
|
||||
confusing - the program flow through a Makefile is hard to trace already,
|
||||
and even harder when it restarts from the beginning because an include file
|
||||
changes at runtime. With redo, you can just read each build script from top
|
||||
to bottom. A `redo-ifchange` call is like calling a function, which you can
|
||||
also read from top to bottom.)
|
||||
|
||||
|
||||
# What projects use redo?
|
||||
|
||||
Some larger proprietary projects are using it, but unfortunately they can't
|
||||
easily be linked from this document. Here are a few open source examples:
|
||||
|
||||
* [Liberation Circuit](https://github.com/linleyh/liberation-circuit) is a
|
||||
straightforward example of a C++ binary (a game) compiled with redo.
|
||||
|
||||
* [WvStreams](https://github.com/apenwarr/wvstreams) uses a more complex
|
||||
setup producing several binaries, libraries, and scripts. It shows how to
|
||||
produce output files in a different directory than the source files.
|
||||
|
||||
* [WvBuild](https://github.com/apenwarr/wvbuild) can cross-compile several
|
||||
dependencies, like openssl and zlib, and then builds WvStreams using those
|
||||
same libraries. It's a good example of redo/make interop and complex
|
||||
dependencies.
|
||||
|
||||
* There's an experimental [variant of
|
||||
Buildroot](https://github.com/apenwarr/buildroot/tree/redo) that uses redo
|
||||
in order to clean up its dependency logic.
|
||||
|
||||
* You can also find some curated examples in the
|
||||
[docs/cookbook/](https://github.com/apenwarr/redo/tree/master/docs/cookbook/) subdir of the redo project itself.
|
||||
|
||||
* A [github search for all.do](https://github.com/search?p=9&q=path%3A%2F+extension%3Ado+filename%3A%2Fall.do&type=Code)
|
||||
shows an ever-growing number of projects using redo.
|
||||
|
||||
If you switch your program's build process to use redo, please let us know and
|
||||
we can link to it here for some free publicity.
|
||||
|
||||
(Please don't use the integration testing code in the redo project's `t/`
|
||||
directory as serious examples of how to use redo. Many of the tests are
|
||||
doing things in intentionally psychotic ways in order to stress redo's code
|
||||
and find bugs.)
|
||||
|
||||
|
||||
# How does this redo compare to other redo implementations?
|
||||
|
||||
djb never released his version, so other people have implemented their own
|
||||
variants based on his [published specification](http://cr.yp.to/redo.html).
|
||||
|
||||
This version, sometimes called apenwarr/redo, is probably the most advanced
|
||||
one, including support for parallel builds,
|
||||
[resilient timestamps](https://apenwarr.ca/log/20181113) and checksums,
|
||||
[build log linearization](https://apenwarr.ca/log/20181106), and
|
||||
helpful debugging features. It's currently written in python for easier
|
||||
experimentation, but the plan is to eventually migrate it to plain C. (Some
|
||||
people like to call this version "python-redo", but I don't like that name.
|
||||
We shouldn't have to rename it when we later transliterate the code to C.)
|
||||
|
||||
Here are some other redo variants (thanks to Nils Dagsson Moskopp for many
|
||||
of these links):
|
||||
|
||||
- Alan Grosskurth's [redo thesis](http://grosskurth.ca/papers/mmath-thesis.pdf)
|
||||
and related sh implementation. (Arguably, this paper is the one that got
|
||||
all the rest of us started.)
|
||||
|
||||
- Nils Dagsson Moskopp's [redo-sh](https://web.archive.org/web/20181106195145/http://news.dieweltistgarnichtso.net/bin/redo-sh.html)
|
||||
is a completely self-sufficient sh-based implementation.
|
||||
|
||||
- apenwarr's [minimal/do](https://github.com/apenwarr/redo/blob/master/minimal/do)
|
||||
is included with this copy of redo. It's also sh-based, but intended to
|
||||
be simple and failsafe, so it doesn't understand how to "redo" targets more
|
||||
than once.
|
||||
|
||||
- Christian Neukirchen's [redo-c](https://github.com/chneukirchen/redo-c), a
|
||||
C implementation.
|
||||
|
||||
- Jonathan de Boyne Pollard's [fork of Alan Grosskurth's redo](http://jdebp.eu./Softwares/redo/grosskurth-redo.html)
|
||||
(another sh-based implementation).
|
||||
|
||||
- Jonathan de Boyne Pollard's [redo](http://jdebp.eu./Softwares/redo/)
|
||||
rewritten in C++
|
||||
|
||||
- Gyepi Sam's [redux](https://github.com/gyepisam/redux) in Go
|
||||
|
||||
- jekor's [redo](https://github.com/jekor/redo) in Haskell
|
||||
|
||||
- Shanti Bouchez-Mongardé (mildred)'s [fork of apenwarr's redo](https://github.com/mildred/redo)
|
||||
in python
|
||||
|
||||
- Tharre's [redo](https://github.com/Tharre/redo) in C
|
||||
|
||||
- catenate's [credo](https://github.com/catenate/credo), a (very
|
||||
rearchitected) variant written for the Inferno Shell.
|
||||
|
||||
The original redo design is so simple and elegant that many individuals
|
||||
have been
|
||||
inspired to (and able to) write their own version of it. In the honoured
|
||||
tradition of Unix's `make`, they (almost) all just use the same name,
|
||||
`redo`. Unfortunately, many of these
|
||||
implementations are unmaintained, slightly incompatible with the "standard"
|
||||
redo semantics, and/or have few or no automated tests.
|
||||
|
||||
At the time of this writing, none of them except apenwarr/redo (ie. this
|
||||
project) support parallel builds (`redo -j`) or log linearization
|
||||
(`redo-log`). For large projects, parallel builds are usually considered
|
||||
essential.
|
||||
|
||||
The [automated tests](https://github.com/apenwarr/redo/tree/master/t) in
|
||||
this version of redo are almost, but not quite, appropriate for testing any
|
||||
redo implementation. If you really must write a new version of redo, we
|
||||
invite you to thoroughly test it against the existing test suite to ensure
|
||||
compatibility. You can also steal our tests (with attribution, of course)
|
||||
and include them in your own source package. We'd also love it it you
|
||||
contribute more automated tests when you find a bug, or send us patches if
|
||||
you find a test which is accidentally incompatible (as opposed to finding a
|
||||
real bug) with other redo implementations.
|
||||
9
docs/md-to-man.do
Normal file
9
docs/md-to-man.do
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
redo-ifchange md2man.py ../redo/whichpython
|
||||
read py <../redo/whichpython
|
||||
if ../redo/python ./md2man.py /dev/null /dev/null >/dev/null; then
|
||||
echo '../redo/python ./md2man.py $2.md.tmp $2.html'
|
||||
else
|
||||
echo "Warning: md2man.py missing modules; can't generate manpages." >&2
|
||||
echo "Warning: try this: sudo easy_install markdown BeautifulSoup" >&2
|
||||
echo 'echo Skipping: $2.1 >&2'
|
||||
fi
|
||||
282
docs/md2man.py
Normal file
282
docs/md2man.py
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
import sys, os, markdown, re
|
||||
from BeautifulSoup import BeautifulSoup
|
||||
|
||||
def _split_lines(s):
|
||||
return re.findall(r'([^\n]*\n?)', s)
|
||||
|
||||
|
||||
class Writer:
|
||||
def __init__(self):
|
||||
self.started = False
|
||||
self.indent = 0
|
||||
self.last_wrote = '\n'
|
||||
|
||||
def _write(self, s):
|
||||
if s:
|
||||
self.last_wrote = s
|
||||
sys.stdout.write(s)
|
||||
|
||||
def writeln(self, s):
|
||||
if s:
|
||||
self.linebreak()
|
||||
self._write('%s\n' % s)
|
||||
|
||||
def write(self, s):
|
||||
if s:
|
||||
self.para()
|
||||
for line in _split_lines(s):
|
||||
if line.startswith('.'):
|
||||
self._write('\\&' + line)
|
||||
else:
|
||||
self._write(line)
|
||||
|
||||
def linebreak(self):
|
||||
if not self.last_wrote.endswith('\n'):
|
||||
self._write('\n')
|
||||
|
||||
def para(self, bullet=None):
|
||||
if not self.started:
|
||||
if not bullet:
|
||||
bullet = ' '
|
||||
if not self.indent:
|
||||
self.writeln(_macro('.PP'))
|
||||
else:
|
||||
assert(self.indent >= 2)
|
||||
prefix = ' '*(self.indent-2) + bullet + ' '
|
||||
self.writeln('.IP "%s" %d' % (prefix, self.indent))
|
||||
self.started = True
|
||||
|
||||
def end_para(self):
|
||||
self.linebreak()
|
||||
self.started = False
|
||||
|
||||
def start_bullet(self):
|
||||
self.indent += 3
|
||||
self.para(bullet='\\[bu]')
|
||||
|
||||
def end_bullet(self):
|
||||
self.indent -= 3
|
||||
self.end_para()
|
||||
|
||||
w = Writer()
|
||||
|
||||
|
||||
def _macro(name, *args):
|
||||
if not name.startswith('.'):
|
||||
raise ValueError('macro names must start with "."')
|
||||
fixargs = []
|
||||
for i in args:
|
||||
i = str(i)
|
||||
i = i.replace('\\', '')
|
||||
i = i.replace('"', "'")
|
||||
if (' ' in i) or not i:
|
||||
i = '"%s"' % i
|
||||
fixargs.append(i)
|
||||
return ' '.join([name] + list(fixargs))
|
||||
|
||||
|
||||
def macro(name, *args):
|
||||
w.writeln(_macro(name, *args))
|
||||
|
||||
|
||||
def _force_string(owner, tag):
|
||||
if tag.string:
|
||||
return tag.string
|
||||
else:
|
||||
out = ''
|
||||
for i in tag:
|
||||
if not (i.string or i.name in ['a', 'br']):
|
||||
raise ValueError('"%s" tags must contain only strings: '
|
||||
'got %r: %r' % (owner.name, tag.name, tag))
|
||||
out += _force_string(owner, i)
|
||||
return out
|
||||
|
||||
|
||||
def _clean(s):
|
||||
s = s.replace('\\', '\\\\')
|
||||
return s
|
||||
|
||||
|
||||
def _bitlist(tag):
|
||||
if getattr(tag, 'contents', None) == None:
|
||||
for i in _split_lines(str(tag)):
|
||||
yield None,_clean(i)
|
||||
else:
|
||||
for e in tag:
|
||||
name = getattr(e, 'name', None)
|
||||
if name in ['a', 'br']:
|
||||
name = None # just treat as simple text
|
||||
s = _force_string(tag, e)
|
||||
if name:
|
||||
yield name,_clean(s)
|
||||
else:
|
||||
for i in _split_lines(s):
|
||||
yield None,_clean(i)
|
||||
|
||||
|
||||
def _bitlist_simple(tag):
|
||||
for typ,text in _bitlist(tag):
|
||||
if typ and not typ in ['em', 'strong', 'code']:
|
||||
raise ValueError('unexpected tag %r inside %r' % (typ, tag.name))
|
||||
yield text
|
||||
|
||||
|
||||
def _text(bitlist):
|
||||
out = ''
|
||||
for typ,text in bitlist:
|
||||
if not typ:
|
||||
out += text
|
||||
elif typ == 'em':
|
||||
out += '\\fI%s\\fR' % text
|
||||
elif typ in ['strong', 'code']:
|
||||
out += '\\fB%s\\fR' % text
|
||||
else:
|
||||
raise ValueError('unexpected tag %r inside text' % (typ,))
|
||||
out = out.strip()
|
||||
out = re.sub(re.compile(r'^\s+', re.M), '', out)
|
||||
return out
|
||||
|
||||
|
||||
def text(tag):
|
||||
w.write(_text(_bitlist(tag)))
|
||||
|
||||
|
||||
# This is needed because .BI (and .BR, .RB, etc) are weird little state
|
||||
# machines that alternate between two fonts. So if someone says something
|
||||
# like foo<b>chicken</b><b>wicken</b>dicken we have to convert that to
|
||||
# .BI foo chickenwicken dicken
|
||||
def _boldline(l):
|
||||
out = ['']
|
||||
last_bold = False
|
||||
for typ,text in l:
|
||||
nonzero = not not typ
|
||||
if nonzero != last_bold:
|
||||
last_bold = not last_bold
|
||||
out.append('')
|
||||
out[-1] += re.sub(r'\s+', ' ', text)
|
||||
macro('.BI', *out)
|
||||
|
||||
|
||||
def do_definition(tag):
|
||||
w.end_para()
|
||||
macro('.TP')
|
||||
w.started = True
|
||||
split = 0
|
||||
pre = []
|
||||
post = []
|
||||
for typ,text in _bitlist(tag):
|
||||
if split:
|
||||
post.append((typ,text))
|
||||
elif text.lstrip().startswith(': '):
|
||||
split = 1
|
||||
post.append((typ,text.lstrip()[2:].lstrip()))
|
||||
else:
|
||||
pre.append((typ,text))
|
||||
_boldline(pre)
|
||||
w.write(_text(post))
|
||||
w.started = False
|
||||
|
||||
|
||||
def do_list(tag):
|
||||
for i in tag:
|
||||
name = getattr(i, 'name', '').lower()
|
||||
if not name and not str(i).strip():
|
||||
pass
|
||||
elif name != 'li':
|
||||
raise ValueError('only <li> is allowed inside <ul>: got %r' % i)
|
||||
else:
|
||||
w.start_bullet()
|
||||
for xi in i:
|
||||
if str(xi).strip():
|
||||
do(xi)
|
||||
w.end_para()
|
||||
w.end_bullet()
|
||||
|
||||
|
||||
def do(tag):
|
||||
name = getattr(tag, 'name', '').lower()
|
||||
if not name:
|
||||
text(tag)
|
||||
elif name == 'h1':
|
||||
macro('.SH', _force_string(tag, tag).upper())
|
||||
w.started = True
|
||||
elif name == 'h2':
|
||||
macro('.SS', _force_string(tag, tag))
|
||||
w.started = True
|
||||
elif name.startswith('h') and len(name)==2:
|
||||
raise ValueError('%r invalid - man page headers must be h1 or h2'
|
||||
% name)
|
||||
elif name == 'pre':
|
||||
t = _force_string(tag.code, tag.code)
|
||||
if t.strip():
|
||||
macro('.RS', '+4n')
|
||||
macro('.nf')
|
||||
w.write(_clean(t).rstrip())
|
||||
macro('.fi')
|
||||
macro('.RE')
|
||||
w.end_para()
|
||||
elif name == 'p' or name == 'br':
|
||||
g = re.match(re.compile(r'([^\n]*)\n *: +(.*)', re.S), str(tag))
|
||||
if g:
|
||||
# it's a definition list (which some versions of python-markdown
|
||||
# don't support, including the one in Debian-lenny, so we can't
|
||||
# enable that markdown extension). Fake it up.
|
||||
do_definition(tag)
|
||||
else:
|
||||
text(tag)
|
||||
w.end_para()
|
||||
elif name == 'ul':
|
||||
do_list(tag)
|
||||
else:
|
||||
raise ValueError('non-man-compatible html tag %r' % name)
|
||||
|
||||
|
||||
PROD='Untitled'
|
||||
VENDOR='Vendor Name'
|
||||
SECTION='9'
|
||||
GROUPNAME='User Commands'
|
||||
DATE=''
|
||||
AUTHOR=''
|
||||
|
||||
lines = []
|
||||
if len(sys.argv) != 3:
|
||||
sys.stderr.write('usage: %s <infile.md> <outfile.html> >outfile.1\n')
|
||||
sys.exit(99)
|
||||
|
||||
infile = sys.argv[1]
|
||||
htmlfile = sys.argv[2]
|
||||
lines += open(infile).read().decode('utf8').split('\n')
|
||||
|
||||
# parse pandoc-style document headers (not part of markdown)
|
||||
g = re.match(r'^%\s+(.*?)\((.*?)\)\s+(.*)$', lines[0])
|
||||
if g:
|
||||
PROD = g.group(1)
|
||||
SECTION = g.group(2)
|
||||
VENDOR = g.group(3)
|
||||
lines.pop(0)
|
||||
g = re.match(r'^%\s+(.*?)$', lines[0])
|
||||
if g:
|
||||
AUTHOR = g.group(1)
|
||||
lines.pop(0)
|
||||
g = re.match(r'^%\s+(.*?)$', lines[0])
|
||||
if g:
|
||||
DATE = g.group(1)
|
||||
lines.pop(0)
|
||||
g = re.match(r'^%\s+(.*?)$', lines[0])
|
||||
if g:
|
||||
GROUPNAME = g.group(1)
|
||||
lines.pop(0)
|
||||
|
||||
inp = '\n'.join(lines)
|
||||
if AUTHOR:
|
||||
inp += ('\n# AUTHOR\n\n%s\n' % AUTHOR).replace('<', '<')
|
||||
|
||||
html = markdown.markdown(inp)
|
||||
open(htmlfile, 'w').write(html)
|
||||
soup = BeautifulSoup(html, convertEntities=BeautifulSoup.HTML_ENTITIES)
|
||||
|
||||
macro('.TH', PROD.upper(), SECTION, DATE, VENDOR, GROUPNAME)
|
||||
macro('.ad', 'l') # left justified
|
||||
macro('.nh') # disable hyphenation
|
||||
for e in soup:
|
||||
do(e)
|
||||
9
docs/mkdocs.do
Normal file
9
docs/mkdocs.do
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
redo-ifchange doc.list
|
||||
xargs redo-ifchange ../mkdocs.yml <doc.list
|
||||
|
||||
if type mkdocs >/dev/null 2>/dev/null; then
|
||||
(cd .. && mkdocs build)
|
||||
else
|
||||
echo "Warning: mkdocs is missing; can't generate website." >&2
|
||||
redo-ifcreate /usr/bin/mkdocs
|
||||
fi
|
||||
49
docs/redo-always.md
Normal file
49
docs/redo-always.md
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# NAME
|
||||
|
||||
redo-always - mark the current target as always needing to be rebuilt
|
||||
|
||||
# SYNOPSIS
|
||||
|
||||
redo-always
|
||||
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
Normally redo-always is run from a .do file that has been
|
||||
executed by `redo`(1). See `redo`(1) for more details.
|
||||
|
||||
redo-always takes no parameters. It simply adds an
|
||||
'impossible' dependency to the current target, which
|
||||
ensures that the target will always be rebuilt if anyone
|
||||
runs `redo-ifchange targetname`.
|
||||
|
||||
Because of the way redo works, `redo-ifchange targetname`
|
||||
will only rebuild `targetname` once per session. So if
|
||||
multiple targets depend on *targetname* and *targetname*
|
||||
has called redo-always, only the first target will cause it
|
||||
to be rebuilt. If the build cycle completes and a new one
|
||||
begins, it will be rebuilt exactly one more time.
|
||||
|
||||
Normally, any target that depends (directly or indirectly)
|
||||
on a sub-target that has called redo-always will also
|
||||
always need to rebuild, since one of its dependencies will
|
||||
always be out of date. To avoid this problem, redo-always is
|
||||
usually used along with `redo-stamp`(1).
|
||||
|
||||
|
||||
# REDO
|
||||
|
||||
Part of the `redo`(1) suite.
|
||||
|
||||
# CREDITS
|
||||
|
||||
The original concept for `redo` was created by D. J.
|
||||
Bernstein and documented on his web site
|
||||
(http://cr.yp.to/redo.html). This independent implementation
|
||||
was created by Avery Pennarun and you can find its source
|
||||
code at http://github.com/apenwarr/redo.
|
||||
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
`redo`(1), `redo-ifcreate`(1), `redo-ifchange`(1), `redo-stamp`(1)
|
||||
92
docs/redo-ifchange.md
Normal file
92
docs/redo-ifchange.md
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
# NAME
|
||||
|
||||
redo-ifchange - rebuild target files when source files have changed
|
||||
|
||||
# SYNOPSIS
|
||||
|
||||
redo-ifchange [targets...]
|
||||
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
Normally redo-ifchange is run from a .do file that has been
|
||||
executed by `redo`(1). See `redo`(1) for more details.
|
||||
|
||||
redo-ifchange doesn't take any command line options other
|
||||
than a list of *targets*. To provide command line options,
|
||||
you need to run `redo` instead.
|
||||
|
||||
redo-ifchange performs the following steps:
|
||||
|
||||
- it creates a dependency on the given *targets*. If any
|
||||
of those targets change in the future, the current target
|
||||
(the one calling redo-ifchange) will marked as needing to
|
||||
be rebuilt.
|
||||
|
||||
- for any *target* that is out of date, it calls the
|
||||
equivalent of `redo target`.
|
||||
|
||||
- for any *target* that is locked (because some other
|
||||
instance of `redo` or `redo-ifchange` is already building
|
||||
it), it waits until the lock is released.
|
||||
|
||||
redo-ifchange returns only after all the given
|
||||
*targets* are known to be up to date.
|
||||
|
||||
|
||||
# TIP 1
|
||||
|
||||
You don't have to run redo-ifchange *before* generating
|
||||
your target; you can generate your target first, then
|
||||
declare its dependencies. For example, as part of
|
||||
compiling a .c file, gcc learns the list
|
||||
of .h files it depends on. You can pass this information
|
||||
along to redo-ifchange, so if any of those headers are
|
||||
changed or deleted, your .c file will be rebuilt:
|
||||
|
||||
redo-ifchange $2.c
|
||||
gcc -o $3 -c $2.c \
|
||||
-MMD -MF $2.deps
|
||||
read DEPS <$2.deps
|
||||
redo-ifchange ${DEPS#*:}
|
||||
|
||||
This is much less confusing than the equivalent
|
||||
autodependency mechanism in `make`(1), because make
|
||||
requires that you declare all your dependencies before
|
||||
running the target build commands.
|
||||
|
||||
|
||||
# TIP 2
|
||||
|
||||
Try to list as many dependencies as possible in a single
|
||||
call to redo-ifchange. Every time you run redo-ifchange,
|
||||
the shell has to fork+exec it, which takes time. Plus redo
|
||||
can only parallelize your build if you give it multiple
|
||||
targets to build at once. It's fine to have a couple of
|
||||
separate redo-ifchange invocations for a particular target
|
||||
when necessary (as in TIP 1 above), but try to keep it to a
|
||||
minimum. For example here's a trick for generating a list
|
||||
of targets, but redo-ifchanging them all at once:
|
||||
|
||||
for d in *.c; do
|
||||
echo ${d%.c}.o
|
||||
done |
|
||||
xargs redo-ifchange
|
||||
|
||||
|
||||
# REDO
|
||||
|
||||
Part of the `redo`(1) suite.
|
||||
|
||||
# CREDITS
|
||||
|
||||
The original concept for `redo` was created by D. J.
|
||||
Bernstein and documented on his web site
|
||||
(http://cr.yp.to/redo.html). This independent implementation
|
||||
was created by Avery Pennarun and you can find its source
|
||||
code at http://github.com/apenwarr/redo.
|
||||
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
`redo`(1), `redo-ifcreate`(1), `redo-always`(1), `redo-stamp`(1)
|
||||
43
docs/redo-ifcreate.md
Normal file
43
docs/redo-ifcreate.md
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# NAME
|
||||
|
||||
redo-ifcreate - rebuild the current target if source files are created
|
||||
|
||||
# SYNOPSIS
|
||||
|
||||
redo-ifcreate [sources...]
|
||||
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
Normally redo-ifcreate is run from a .do file that has been
|
||||
executed by `redo`(1). See `redo`(1) for more details.
|
||||
|
||||
redo-ifcreate takes a list of nonexistent files (*sources*)
|
||||
and adds them as dependencies to the current target (the
|
||||
one calling redo-ifcreate). If any of those files are
|
||||
created in the future, the target will be marked as needing
|
||||
to be rebuilt.
|
||||
|
||||
If one of the given files exists at the time redo-ifcreate
|
||||
is called, it will return a nonzero exit code.
|
||||
|
||||
If you want to declare dependencies on files that already
|
||||
exist, use `redo-ifchange`(1) instead.
|
||||
|
||||
|
||||
# REDO
|
||||
|
||||
Part of the `redo`(1) suite.
|
||||
|
||||
# CREDITS
|
||||
|
||||
The original concept for `redo` was created by D. J.
|
||||
Bernstein and documented on his web site
|
||||
(http://cr.yp.to/redo.html). This independent implementation
|
||||
was created by Avery Pennarun and you can find its source
|
||||
code at http://github.com/apenwarr/redo.
|
||||
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
`redo`(1), `redo-ifchange`(1), `redo-always`(1), `redo-stamp`(1)
|
||||
83
docs/redo-log.md
Normal file
83
docs/redo-log.md
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
# NAME
|
||||
|
||||
redo-log - display log messages from previous builds
|
||||
|
||||
# SYNOPSIS
|
||||
|
||||
redo-log [options...] [targets...]
|
||||
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
When redo runs, it captures the standard error (stderr) output from the
|
||||
build activity for each target, and saves it for later. When a target is
|
||||
rebuilt, the new logs replace the old logs for that target. redo-log
|
||||
prints the log output for any set of targets.
|
||||
|
||||
# OPTIONS
|
||||
|
||||
-r, --recursive
|
||||
: if the requested targets cause any dependencies to be built, recursively
|
||||
show the logs from those dependencies as well. (And if those
|
||||
dependencies build further dependencies, also show those logs, and so on.)
|
||||
|
||||
-u, --unchanged
|
||||
: show messages even for dependencies that were unchanged (did not need to be
|
||||
rebuilt). To do this, we show the logs for the *most recent* build of
|
||||
each affected dependency. Usually this is used with `-r`.
|
||||
|
||||
-f, --follow
|
||||
: if a build is currently running for any of the requested targets or
|
||||
their dependencies, follow the logs (like `tail -f`) until the build
|
||||
finishes.
|
||||
|
||||
--no-details
|
||||
: display *only* the messages from redo itself, not the other messages
|
||||
produced by build scripts. Generally this gives you a list of which
|
||||
targets were built, but not detailed logs, warnings, or errors.
|
||||
|
||||
--no-status
|
||||
: don't display the running build status at the bottom of the screen.
|
||||
(Unless this option is specified, the status line will be enabled
|
||||
if using --follow, if stderr is a terminal.) If stderr is *not* a
|
||||
terminal, you can force enable the status line using --status.
|
||||
|
||||
--no-pretty
|
||||
: display "raw" redo log lines (@@REDO events) rather than using a
|
||||
human-readable format. The default is --pretty.
|
||||
|
||||
--no-color
|
||||
: when using --pretty and writing to a terminal, colorize the output to
|
||||
make results stand out more clearly. If not writing to a terminal, you
|
||||
can use --color to force colorized output.
|
||||
|
||||
--debug-locks
|
||||
: print messages about acquiring, releasing, and waiting
|
||||
on locks. Because redo can be highly parallelized,
|
||||
one instance may end up waiting for a target to be
|
||||
built by some other instance before it can continue.
|
||||
If you suspect this is causing troubles, use this
|
||||
option to see which instance is waiting and when.
|
||||
|
||||
--debug-pids
|
||||
: add the process id of the particular redo instance to each
|
||||
output message. This makes it easier to figure out
|
||||
which sub-instance of redo is doing what.
|
||||
|
||||
|
||||
# REDO
|
||||
|
||||
Part of the `redo`(1) suite.
|
||||
|
||||
# CREDITS
|
||||
|
||||
The original concept for `redo` was created by D. J.
|
||||
Bernstein and documented on his web site
|
||||
(http://cr.yp.to/redo.html). This independent implementation
|
||||
was created by Avery Pennarun and you can find its source
|
||||
code at http://github.com/apenwarr/redo.
|
||||
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
`redo`(1)
|
||||
55
docs/redo-ood.md
Normal file
55
docs/redo-ood.md
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# NAME
|
||||
|
||||
redo-ood - print the list of out-of-date redo targets
|
||||
|
||||
# SYNOPSIS
|
||||
|
||||
redo-ood
|
||||
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
redo-ood prints a list of all redo *target* files that
|
||||
exist, but are out of date.
|
||||
|
||||
Files that no longer exist might not be targets anymore;
|
||||
you'll have to redo them for them to end up back in this
|
||||
list. (For example, if you built a file and then removed
|
||||
the file and its .do file, you wouldn't want it to show up
|
||||
in this list.)
|
||||
|
||||
If a .do script does not produce an output file (eg.
|
||||
all.do, clean.do), it also does not show up in this list.
|
||||
|
||||
Each filename is on a separate line. The filenames are not
|
||||
guaranteed to be in any particular order.
|
||||
|
||||
All filenames are printed relative the current directory.
|
||||
The list is not filtered in any way; it contains *all* the
|
||||
target filenames from the entire project. Remember that
|
||||
the redo database may span more than just your project, so
|
||||
you might need to filter the list before using it. (A
|
||||
useful heuristic might be to remove any line starting
|
||||
with '../' since it often refers to a target you don't care
|
||||
about.)
|
||||
|
||||
If you want a list of all targets, not just out-of-date
|
||||
ones, use `redo-targets`(1).
|
||||
|
||||
|
||||
# REDO
|
||||
|
||||
Part of the `redo`(1) suite.
|
||||
|
||||
# CREDITS
|
||||
|
||||
The original concept for `redo` was created by D. J.
|
||||
Bernstein and documented on his web site
|
||||
(http://cr.yp.to/redo.html). This independent implementation
|
||||
was created by Avery Pennarun and you can find its source
|
||||
code at http://github.com/apenwarr/redo.
|
||||
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
`redo`(1), `redo-targets`(1), `redo-sources`(1)
|
||||
48
docs/redo-sources.md
Normal file
48
docs/redo-sources.md
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# NAME
|
||||
|
||||
redo-sources - print the list of all known redo sources
|
||||
|
||||
# SYNOPSIS
|
||||
|
||||
redo-sources
|
||||
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
redo-sources prints a list of all redo *source* files that
|
||||
still exist.
|
||||
|
||||
A source file is any file that has been listed as a
|
||||
dependency (using `redo-ifchange`(1) or `redo-ifcreate`(1))
|
||||
but is not itself a target. A target is a file that
|
||||
`redo`(1) can build using a .do script.
|
||||
|
||||
Each filename is on a separate line. The filenames are not
|
||||
guaranteed to be in any particular order.
|
||||
|
||||
All filenames are printed relative the current directory.
|
||||
The list is not filtered in any way; it contains *all* the
|
||||
source filenames from the entire project. Remember that
|
||||
the redo database may span more than just your project, so
|
||||
you might need to filter the list before using it.
|
||||
|
||||
If you want a list of targets instead of sources, use
|
||||
`redo-targets`(1) or `redo-ood`(1).
|
||||
|
||||
|
||||
# REDO
|
||||
|
||||
Part of the `redo`(1) suite.
|
||||
|
||||
# CREDITS
|
||||
|
||||
The original concept for `redo` was created by D. J.
|
||||
Bernstein and documented on his web site
|
||||
(http://cr.yp.to/redo.html). This independent implementation
|
||||
was created by Avery Pennarun and you can find its source
|
||||
code at http://github.com/apenwarr/redo.
|
||||
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
`redo`(1), `redo-targets`(1), `redo-ood`(1)
|
||||
110
docs/redo-stamp.md
Normal file
110
docs/redo-stamp.md
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
# NAME
|
||||
|
||||
redo-stamp - detect if the current target has meaningfully changed
|
||||
|
||||
# SYNOPSIS
|
||||
|
||||
redo-stamp <$3
|
||||
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
Normally, when `redo`(1) builds a target T, all the other
|
||||
targets that depend on T are marked as out of date. Even
|
||||
if the rebuilt T is identical to the old one, all its
|
||||
dependents need to be rebuilt.
|
||||
|
||||
By calling redo-stamp from your .do script, you can tell
|
||||
`redo` that even though the current target is building, its
|
||||
output may turn out to be unchanged. If it hasn't, `redo`
|
||||
may then be able to avoid building other targets that
|
||||
depend on this target.
|
||||
|
||||
redo-stamp marks the current target as changed or unchanged
|
||||
by comparing its stdin to the input that was provided last
|
||||
time redo-stamp was called for this target.
|
||||
|
||||
The stamp data can be anything you want. Some possibilities
|
||||
are:
|
||||
|
||||
- the actual target file contents:
|
||||
|
||||
redo-stamp <$3
|
||||
|
||||
- a list of filenames:
|
||||
|
||||
find -name '*.[ch]' | sort | redo-stamp
|
||||
|
||||
- the contents of a web page:
|
||||
|
||||
curl http://example.org | redo-stamp
|
||||
|
||||
To ensure that your target gets checked every time, you
|
||||
might want to use `redo-always`(1).
|
||||
|
||||
|
||||
# DISCUSSION
|
||||
|
||||
While using redo-stamp is simple, the way it
|
||||
works is harder to explain. Deciding if a target is
|
||||
up to date or not is the job of `redo-ifchange`(1).
|
||||
Normally, a target is considered out of date when any of its
|
||||
dependencies (direct or indirect) have changed. When that
|
||||
happens, `redo-ifchange` runs the .do script for the
|
||||
target, which regenerates the entire dependency list,
|
||||
including rebuilding any dependencies as necessary.
|
||||
|
||||
Imagine that we have the following dependency chain:
|
||||
|
||||
T -> X -> Y -> Z
|
||||
|
||||
T depends on X, which depends on Y, which depends
|
||||
on Z. Now imagine someone has changed Z.
|
||||
|
||||
If someone runs `redo-ifchange T`, then redo-ifchange
|
||||
checks if X is up to date; to do that, it checks if Y
|
||||
is up to date; and to do that, it checks whether Z is up to
|
||||
date - which it isn't. Thus, Y is not up to date, which
|
||||
means X isn't, which means T isn't either, and so we need
|
||||
to run T.do. `redo-ifchange` won't directly `redo X` just
|
||||
because X is dirty; it redoes T, and T.do might eventually
|
||||
call `redo-ifchange X` if it needs to.
|
||||
|
||||
When using redo-stamp, the behaviour is different. Let's
|
||||
say Y has used redo-stamp. In the above sequence, Y is
|
||||
outdated because Z has changed. However, we don't know yet
|
||||
if Y's stamp has changed, so we don't yet know if we need
|
||||
to redo X, and thus we don't know if we need to redo T. We
|
||||
can't just run `redo T`, because that could waste a lot of
|
||||
time if it turns out T didn't need to be rebuilt after all.
|
||||
|
||||
What we do instead is note whether the *only* dependencies
|
||||
of T that are out of date are 'stamped' targets. If *any*
|
||||
dependencies of T are normal, out-of-date redo targets,
|
||||
then T needs to be rebuilt anyway; we don't have to do
|
||||
anything special.
|
||||
|
||||
If the only dependency of T that has changed is Y (a
|
||||
'stamped' target), then we need to `redo Y` automatically
|
||||
in order to determine if T needs to be rebuilt. This is
|
||||
the only time that `redo` ever rebuilds a target that
|
||||
hasn't been explicitly asked for as part of a running .do
|
||||
script.
|
||||
|
||||
|
||||
# REDO
|
||||
|
||||
Part of the `redo`(1) suite.
|
||||
|
||||
# CREDITS
|
||||
|
||||
The original concept for `redo` was created by D. J.
|
||||
Bernstein and documented on his web site
|
||||
(http://cr.yp.to/redo.html). This independent implementation
|
||||
was created by Avery Pennarun and you can find its source
|
||||
code at http://github.com/apenwarr/redo.
|
||||
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
`redo`(1), `redo-ifcreate`(1), `redo-ifchange`(1), `redo-always`(1)
|
||||
60
docs/redo-targets.md
Normal file
60
docs/redo-targets.md
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
# NAME
|
||||
|
||||
redo-targets - print the list of all known redo targets
|
||||
|
||||
# SYNOPSIS
|
||||
|
||||
redo-targets
|
||||
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
redo-targets prints a list of all redo *target* files that
|
||||
still exist.
|
||||
|
||||
Files that no longer exist might not be targets anymore;
|
||||
you'll have to redo them for them to end up back in this
|
||||
list. (For example, if you built a file and then removed
|
||||
the file and its .do file, you wouldn't want it to show up
|
||||
in this list.)
|
||||
|
||||
If a .do script does not produce an output file (eg.
|
||||
all.do, clean.do), it also does not show up in this list.
|
||||
|
||||
The output of redo-targets might be useful in a
|
||||
semi-automated `clean.do` target; you could delete all the
|
||||
known targets, thus forcing them to be rebuilt next time.
|
||||
|
||||
Each filename is on a separate line. The filenames are not
|
||||
guaranteed to be in any particular order.
|
||||
|
||||
All filenames are printed relative the current directory.
|
||||
The list is not filtered in any way; it contains *all* the
|
||||
target filenames from the entire project. Remember that
|
||||
the redo database may span more than just your project, so
|
||||
you might need to filter the list before using it. (A
|
||||
useful heuristic might be to remove any line starting with
|
||||
'../' since it often refers to a target you don't care
|
||||
about.)
|
||||
|
||||
If you want a list of only out-of-date targets, use
|
||||
`redo-ood`(1). If you want a list of sources (dependencies
|
||||
that aren't targets), use `redo-sources`(1).
|
||||
|
||||
|
||||
# REDO
|
||||
|
||||
Part of the `redo`(1) suite.
|
||||
|
||||
# CREDITS
|
||||
|
||||
The original concept for `redo` was created by D. J.
|
||||
Bernstein and documented on his web site
|
||||
(http://cr.yp.to/redo.html). This independent implementation
|
||||
was created by Avery Pennarun and you can find its source
|
||||
code at http://github.com/apenwarr/redo.
|
||||
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
`redo`(1), `redo-ood`(1), `redo-sources`(1)
|
||||
104
docs/redo-whichdo.md
Normal file
104
docs/redo-whichdo.md
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
# NAME
|
||||
|
||||
redo-whichdo - show redo's search path for a .do file
|
||||
|
||||
# SYNOPSIS
|
||||
|
||||
redo-whichdo <target>
|
||||
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
`redo`(1) and `redo-ifchange`(1) build their targets by executing a ".do
|
||||
file" script with appropriate arguments. .do files are searched starting
|
||||
from the directory containing the target, and if not found there, up the
|
||||
directory tree until a match is found.
|
||||
|
||||
To help debugging your scripts when redo is using an unexpected .do file, or
|
||||
to write advanced scripts that "proxy" from one .do file to another, you
|
||||
can use `redo-whichdo` to see the exact search path that `redo` uses.
|
||||
|
||||
The output format lists potential .do files, one per line, in order of
|
||||
preference, separated by newline characters, and stopping once a
|
||||
matching .do file has been found. If the return code is zero,
|
||||
the last line is a .do file that actually exists; otherwise the entire
|
||||
search path has been exhausted (and printed).
|
||||
|
||||
|
||||
# EXAMPLE
|
||||
|
||||
Here's a typical search path for a source file (`x/y/a.b.o`). Because the
|
||||
filename contains two dots (.), at each level of the hierarchy, `redo` needs
|
||||
to search `default.b.o.do`, `default.o.do`, and `default.do`.
|
||||
```sh
|
||||
$ redo-whichdo x/y/a.b.o; echo $?
|
||||
|
||||
x/y/a.b.o.do
|
||||
x/y/default.b.o.do
|
||||
x/y/default.o.do
|
||||
x/y/default.do
|
||||
x/default.b.o.do
|
||||
x/default.o.do
|
||||
x/default.do
|
||||
default.b.o.do
|
||||
default.o.do
|
||||
0
|
||||
```
|
||||
You might use `redo-whichdo` to delegate from one .do script to another,
|
||||
using code like the following. This gets a little tricky because not only
|
||||
are you finding a new .do file, but you have `cd` to the .do file
|
||||
directory and adjust `$1` and `$2` appropriately.
|
||||
|
||||
ofile=$PWD/$3
|
||||
x1=$1
|
||||
cd "$SRCDIR"
|
||||
redo-whichdo "$x1" | {
|
||||
ifcreate=
|
||||
while read dopath; do
|
||||
if [ ! -e "$dopath" ]; then
|
||||
ifcreate="$ifcreate $dopath"
|
||||
else
|
||||
redo-ifcreate $ifcreate
|
||||
redo-ifchange "$dopath"
|
||||
|
||||
dofile=${dopath##*/}
|
||||
dodir=${dopath%$dofile}
|
||||
|
||||
# Create updated $1 and $2 for the new .do file
|
||||
x1_rel=${x1#$dodir}
|
||||
ext=${dofile##*default}
|
||||
if [ "$ext" != "$dofile" ]; then
|
||||
ext=${ext%.do}
|
||||
else
|
||||
ext=''
|
||||
fi
|
||||
x2_rel=${x1#$dodir}
|
||||
x2_rel=${x2_rel%$ext}
|
||||
|
||||
cd "$dodir"
|
||||
|
||||
set -- "$x1_rel" "$x2_rel" "$ofile"
|
||||
. "./$dofile"
|
||||
exit
|
||||
fi
|
||||
done
|
||||
exit 3
|
||||
}
|
||||
|
||||
|
||||
# REDO
|
||||
|
||||
Part of the `redo`(1) suite.
|
||||
|
||||
# CREDITS
|
||||
|
||||
The original concept for `redo` was created by D. J.
|
||||
Bernstein and documented on his web site
|
||||
(http://cr.yp.to/redo.html). This independent implementation
|
||||
was created by Avery Pennarun and you can find its source
|
||||
code at http://github.com/apenwarr/redo.
|
||||
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
`redo`(1), `redo-ifchange`(1), `redo-ifcreate`(1)
|
||||
290
docs/redo.md
Normal file
290
docs/redo.md
Normal file
|
|
@ -0,0 +1,290 @@
|
|||
# NAME
|
||||
|
||||
redo - rebuild target files when source files have changed
|
||||
|
||||
# SYNOPSIS
|
||||
|
||||
redo [options...] [targets...]
|
||||
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
redo is a simple yet powerful tool for rebuilding target
|
||||
files, and any of their dependencies, based on a set of
|
||||
rules. The rules are encoded in simple `sh`(1) scripts
|
||||
called '.do scripts.'
|
||||
|
||||
redo supports GNU `make`(1)-style parallel builds using the
|
||||
`-j` option; in fact, redo's parallel jobserver is compatible
|
||||
with GNU Make, so redo and make can share build tokens with
|
||||
each other. redo can call a sub-make (eg. to build a
|
||||
subproject that uses Makefiles) or vice versa (eg. if a
|
||||
make-based project needs to build a redo-based subproject).
|
||||
|
||||
Unlike make, redo does not have any special syntax of its
|
||||
own; each *target* is built by running a .do file, which is
|
||||
simply a shell script that redo executes for you with a
|
||||
particular environment and command-line arguments.
|
||||
|
||||
If no *targets* are specified, redo pretends you specified
|
||||
exactly one target named `all`.
|
||||
|
||||
Note that redo *always* rebuilds the given targets
|
||||
(although it may skip rebuilding the targets' dependencies
|
||||
if they are up to date). If you only want to rebuild
|
||||
targets that are not up to date, use `redo-ifchange`(1)
|
||||
instead.
|
||||
|
||||
A .do script can call redo recursively to build its
|
||||
dependencies.
|
||||
|
||||
To avoid confusion caused by multiple programs trying to
|
||||
use the terminal, inside .do scripts, stdin is normally
|
||||
redirected to /dev/null. The only exception is if the `-j`
|
||||
option is *not* given and `--no-log` is used.
|
||||
|
||||
|
||||
# OPTIONS
|
||||
|
||||
-j, --jobs=*maxjobs*
|
||||
: execute at most *maxjobs* .do scripts in parallel. The
|
||||
default value is 1.
|
||||
|
||||
-d, --debug
|
||||
: print dependency checks as they happen. You can use
|
||||
this to figure out why a particular target is/isn't being
|
||||
rebuilt when your .do script calls it using
|
||||
`redo-ifchange`.
|
||||
|
||||
-v, --verbose
|
||||
: pass the -v option to /bin/sh when executing scripts.
|
||||
This normally causes the shell to echo the .do script lines
|
||||
to stderr as it reads them. Most shells will print the
|
||||
exact source line (eg. `echo $3`) and not the
|
||||
substituted value of variables (eg. `echo
|
||||
mytarget.redo.tmp`).
|
||||
|
||||
-x, --xtrace
|
||||
: pass the -x option to /bin/sh when executing scripts.
|
||||
This normally causes the shell to echo exactly which
|
||||
commands are being executed. Most shells will print
|
||||
the substituted variables (eg. `echo
|
||||
mytarget.redo.tmp`) and not the original source line
|
||||
(eg. `echo $3`).
|
||||
|
||||
-k, --keep-going
|
||||
: keep building as many targets as possible even if some
|
||||
of them return an error. If one target fails, any
|
||||
target that depends on it also cannot be built, of course.
|
||||
|
||||
--shuffle
|
||||
: randomize the order in which requested targets are
|
||||
built. Normally, if you run `redo a b c`, the targets
|
||||
will be built exactly in that order: first `a`, then
|
||||
`b`, then `c`. But if you use `-j`, they might end up
|
||||
being built in parallel, so it isn't safe to rely on
|
||||
this precise ordering. Using `--shuffle`, redo will
|
||||
build its targets in random order even without `-j`,
|
||||
which makes it easier to find accidental dependency
|
||||
problems of this sort. NOTE: if you really just want
|
||||
to guarantee that `a` is built, then `b`, then `c`, you
|
||||
can just run three `redo` commands consecutively.
|
||||
Because your .do script is just a script, it will not
|
||||
be accidentally parallelized.
|
||||
|
||||
--no-details
|
||||
: display *only* the messages from redo itself, not the other messages
|
||||
produced by build scripts. Generally this gives you a list of which
|
||||
targets were built, but not detailed logs, warnings, or errors.
|
||||
|
||||
--no-status
|
||||
: don't display the running build status at the bottom of the screen.
|
||||
(Unless this option is specified, the status line will be enabled
|
||||
if using --follow, if stderr is a terminal.) If stderr is *not* a
|
||||
terminal, you can force enable the status line using --status.
|
||||
|
||||
--no-pretty
|
||||
: display "raw" redo log lines (@@REDO events) rather than using a
|
||||
human-readable format. The default is --pretty.
|
||||
|
||||
--no-color
|
||||
: when using --pretty and writing to a terminal, colorize the output to
|
||||
make results stand out more clearly. If not writing to a terminal, you
|
||||
can use --color to force colorized output.
|
||||
|
||||
--no-log
|
||||
: don't capture stderr log messages from build scripts. This prevents
|
||||
redo-log from redisplaying the logs later, and if using --jobs, causes
|
||||
output from all parallel jobs to be jumbled together. This was the
|
||||
only behaviour available before redo-0.30. The default is --log.
|
||||
|
||||
--debug-locks
|
||||
: print messages about acquiring, releasing, and waiting
|
||||
on locks. Because redo can be highly parallelized,
|
||||
one instance may end up waiting for a target to be
|
||||
built by some other instance before it can continue.
|
||||
If you suspect this is causing troubles, use this
|
||||
option to see which instance is waiting and when.
|
||||
|
||||
--debug-pids
|
||||
: add the process id of the particular redo instance to each
|
||||
output message. This makes it easier to figure out
|
||||
which sub-instance of redo is doing what.
|
||||
|
||||
--version
|
||||
: displays the redo version number.
|
||||
|
||||
|
||||
# DISCUSSION
|
||||
|
||||
The core of redo is extremely simple. When you type `redo
|
||||
targetname`, then it will search for a matching .do file
|
||||
based on a simple algorithm. For example, given a target
|
||||
named `mytarget.a.b.c.d`, redo will look for a .do file in
|
||||
the following order:
|
||||
|
||||
- mytarget.a.b.c.d.do
|
||||
- default.a.b.c.d.do
|
||||
- default.b.c.d.do
|
||||
- default.c.d.do
|
||||
- default.d.do
|
||||
- default.do
|
||||
|
||||
In all cases, the .do file must be in the same directory as
|
||||
the target file, or in one of the target's parent
|
||||
directories. For example, if given a target named
|
||||
`../a/b/xtarget.y`, redo will look for a .do file in the
|
||||
following order:
|
||||
|
||||
- $PWD/../a/b/xtarget.y.do
|
||||
- $PWD/../a/b/default.y.do
|
||||
- $PWD/../a/b/default.do
|
||||
- $PWD/../a/default.y.do
|
||||
- $PWD/../a/default.do
|
||||
- $PWD/../default.y.do
|
||||
- $PWD/../default.do
|
||||
|
||||
The first matching .do file is executed as a `/bin/sh`
|
||||
script. The .do script is always executed with the current
|
||||
working directory set to the directory containing the .do
|
||||
file. Because of that rule, the
|
||||
following two commands always have exactly identical
|
||||
behaviour:
|
||||
|
||||
redo path/to/target
|
||||
|
||||
cd path/to && redo target
|
||||
|
||||
(Note: in `make`(1), these commands have confusingly
|
||||
different semantics. The first command would look for a
|
||||
target named `path/to/target` in `./Makefile`, while the
|
||||
second command would look for a target named `target` in
|
||||
`./path/to/Makefile`. The two Makefiles might give
|
||||
completely different results, and it's likely that the
|
||||
first command would have incomplete dependency information.
|
||||
redo does not have this problem.)
|
||||
|
||||
The three arguments passed to the .do script are:
|
||||
|
||||
- $1: the target name (eg. mytarget.a.b)
|
||||
- $2: the basename of the target, minus its extension (eg. mytarget)
|
||||
- $3: a temporary filename that the .do script should write
|
||||
its output to.
|
||||
|
||||
Instead of using $3, the .do script may also write the
|
||||
produced data to stdout.
|
||||
|
||||
If the .do file is in the same directory as the target, $1
|
||||
is guaranteed to be a simple filename (with no path
|
||||
component). If the .do file is in a parent directory of
|
||||
the target, $1 and $3 will be relative paths (ie. will
|
||||
contain slashes).
|
||||
|
||||
redo is designed to update its targets atomically, and only
|
||||
if the do script succeeds (ie. returns a zero exit code).
|
||||
Thus, you should never write directly to the target file,
|
||||
only to $3 or stdout.
|
||||
|
||||
Normally, a .do script will call other .do scripts
|
||||
recursively, by running either `redo` (which will always
|
||||
build the sub-target) or `redo-ifchange` (which only
|
||||
rebuilds the sub-target if its dependencies have changed).
|
||||
|
||||
Running `redo-ifchange` is also the way your .do script
|
||||
declares dependencies on other targets; any target that is
|
||||
`redo-ifchange`d during your .do script's execution is both
|
||||
executed (if needed) and added as a dependency.
|
||||
|
||||
You may have heard that 'recursive make is considered
|
||||
harmful' (http://miller.emu.id.au/pmiller/books/rmch/).
|
||||
Unlike `make`(1), redo does correct locking, state
|
||||
management, and global dependency checking, so none of the
|
||||
arguments in that essay apply to redo. In fact, recursive
|
||||
redo is really the only kind of redo.
|
||||
|
||||
|
||||
# RELATED COMMANDS
|
||||
|
||||
When writing a .do script, it will probably need to run
|
||||
one or more of the following commands:
|
||||
|
||||
`redo [targets...]`
|
||||
: to build a sub-target unconditionally.
|
||||
|
||||
`redo-ifchange [targets...]`
|
||||
: to declare dependencies on the given files, and build them if
|
||||
they don't yet exist or are outdated.
|
||||
|
||||
`redo-ifcreate [sources...]`
|
||||
: to tell redo that the current target must be rebuilt if
|
||||
the given source files (which must not yet exist) get created.
|
||||
|
||||
`redo-always`
|
||||
: to tell redo that the current target must always be
|
||||
rebuilt, even if none of its dependencies have changed.
|
||||
(You might need this for targets that depend on more than just
|
||||
file contents. For example, the output of `ls *.c`
|
||||
changes when files are created or deleted, but there is no way
|
||||
for redo to know that without re-running the command.)
|
||||
|
||||
`echo "stamp contents..." | redo-stamp`
|
||||
: to tell redo that even though the current target has
|
||||
been rebuilt, it may not actually be any different from
|
||||
the previous version, so targets that depend on it
|
||||
might not need to be rebuilt. Often used in
|
||||
conjunction with `redo-always` to reduce the impact of
|
||||
always rebuilding a target.
|
||||
|
||||
|
||||
There are also some less common ones:
|
||||
|
||||
`redo-ood`
|
||||
: Get a list of all known targets that have been built before, but
|
||||
are currently out of date.
|
||||
|
||||
`redo-targets`
|
||||
: Get a list of all known targets that have been built before.
|
||||
|
||||
`redo-sources`
|
||||
: Get a list of all known redo source files that still exist.
|
||||
|
||||
`redo-whichdo <target>`
|
||||
: Explain the search path used to find a .do file for the given
|
||||
target.
|
||||
|
||||
|
||||
# CREDITS
|
||||
|
||||
The original concept for `redo` was created by D. J.
|
||||
Bernstein and documented on his web site
|
||||
(http://cr.yp.to/redo.html). This independent implementation
|
||||
was created by Avery Pennarun and you can find its source
|
||||
code at http://github.com/apenwarr/redo.
|
||||
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
`sh`(1), `make`(1),
|
||||
`redo-ifchange`(1), `redo-ifcreate`(1), `redo-always`(1),
|
||||
`redo-stamp`(1), `redo-ood`(1), `redo-targets`(1), `redo-sources`(1),
|
||||
`redo-whichdo`(1)
|
||||
87
docs/t/testitem.md
Normal file
87
docs/t/testitem.md
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
% redo(1) Redo %VERSION%
|
||||
% Avery Pennarun <apenwarr@gmail.com>
|
||||
% %DATE%
|
||||
|
||||
# NAME
|
||||
|
||||
redo-always - mark the current target as always needing to be rebuilt
|
||||
|
||||
# SYNOPSIS
|
||||
|
||||
redo-always
|
||||
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
Normally redo-always is run from a .do file that has been
|
||||
executed by `redo`(1). See `redo`(1) for more details.
|
||||
|
||||
This is a "quoted string."
|
||||
|
||||
"entirely quoted"
|
||||
|
||||
.starting with dot
|
||||
|
||||
I'm a \big \\backslasher!
|
||||
|
||||
This **line** has _multiple_ `formats`(interspersed) *with* each _other_
|
||||
and *here is a multi
|
||||
line italic.*
|
||||
|
||||
This line has an & and a < and a >.
|
||||
|
||||
- "line starting with quoted"
|
||||
|
||||
Here's some code
|
||||
with indentation
|
||||
yay! (a \backslash and <brackets>)
|
||||
"foo"
|
||||
|
||||
skipped a line
|
||||
indented
|
||||
|
||||
another skip
|
||||
|
||||
- -starting with dash
|
||||
|
||||
- .starting with dot
|
||||
|
||||
chicken
|
||||
|
||||
- list item
|
||||
with more text
|
||||
|
||||
and even more
|
||||
|
||||
- second list
|
||||
- third list
|
||||
|
||||
wicken
|
||||
|
||||
- list 1a
|
||||
- list 1b
|
||||
- list 2
|
||||
- list 3
|
||||
|
||||
barf
|
||||
|
||||
First line
|
||||
: definition list.
|
||||
with
|
||||
multiple
|
||||
lines!
|
||||
|
||||
--item=*value*
|
||||
: a description.
|
||||
|
||||
`-x`
|
||||
: more stuff. if you had a lot of text, this is what it
|
||||
would look like. It goes on and on and on.
|
||||
|
||||
a line with *altogether* "too much" stuff on it to realistically *make* it in a definition list
|
||||
: and yet, here we are.
|
||||
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
`redo`(1), `redo-ifcreate`(1), `redo-ifchange`(1), `redo-stamp`(1)
|
||||
4
docs/test.do
Normal file
4
docs/test.do
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# We don't normally build the cookbook examples, because they're
|
||||
# not really part of redo itself. But build them when testing,
|
||||
# as a basic check that redo (and the examples) are valid.
|
||||
redo cookbook/all
|
||||
Loading…
Add table
Add a link
Reference in a new issue