This is the mail archive of the crossgcc@sourceware.org mailing list for the crossgcc project.

See the CrossGCC FAQ for lots more information.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: [crosstool-NG] Design discussion


On Saturday 04 April 2009 13:14:20 Yann E. MORIN wrote:

Ok, I think I left off around here:

> 2.b) Ease configuration of the toolchain
>
> In the state, configuring crosstool required editing a file containing
> shell variables assignements. There was no proper documentation at what
> variables were used, and no clear explanations about each variables
> meaning.

My response to this problem was to write documentation.

Here's my file containing every configuration value used by build.sh or one of 
the scripts it calls:

  http://impactlinux.com/hg/firmware/file/tip/config

Each of those variables defaults to blank.  You only set it if you want to 
change that default value.  There's a comment right before it explaining what 
it does.  You can set them in your environment, or set them in that file, 
either way.

> The need for a proper way to configure a toolchain arose, and I quite
> instinctively turned to the configuration scheme used by the Linux
> kernel. This kconfig language is easy to write. The frontends that
> then present the resulting menuconfig have limitations in some corner
> cases, but they are maintained by the kernel folks.

While I've used kconfig myself, there's an old saying: "If all you have is a 
hammer, everything looks like a nail".

The failure mode of kconfig is having so much granularity that your users wind 
up being the guy standing at the register at Starbucks going "I just want a 
coffee!"  (Not sure if that reference translates.)

Ironically, kconfig is only really worth using when you have enough config 
options to bother with it.  When you have small numbers of config options 
that are usually going to be off, I prefer environment variables (with a 
config file in which you can set those in a persistent manner) or command 
line options.  Since you can set an environment variable on the command line, 
ala:

  FORK=1 ./buildall.sh

I lean towards those.  Possibly a matter of personal taste...

> Again, of with the build scripts, above, I decided to split each components
> configuration into separate files, with an almost 1-to-1 mapping.

I did that in an earlier version of my build scripts (the one available in 
the "old" directory).

But the thing is, doing that assumes each build component is big and evil and 
fiddly, and that makes them tend to _become_ big and evil and fiddling.  For 
example, your "binutils.sh" is 119 lines.  Mine's 16, including a comment and 
two blank lines.

Ok, a more fair comparison would include both the cross and native binutils 
builds (add another 15 lines for the native one, again with two blank lines 
and a comment), plus the download.sh call to the download function for 
binutils (6 lines, of which only three are needed: one setting the URL, one 
setting the SHA1SUM, and one calling download.)

So 37 lines vs your 119.

The other thing is that having the build be in one file makes the 
relationships between components very obvious.  An extremely important piece 
of information in Linux From Scratch is what _order_ you have to build the 
packages in, since everything depends on everything else and the hard part is 
breaking circular dependencies.

My cross-compiler.sh is a shell script, 150 lines long, that builds a cross 
compiler.  It builds and installs binutils, builds and installs gcc, adjusts 
them into a relocatable form, installs the linux kernel headers, builds and 
installs uClibc, creates a README out of a here document, makes a tarball of 
the result, and then runs a sanity test on the newly created cross compiler 
by building "hello world" with it (once dynamically linked and once 
statically linked, and optionally runs qemu application emulation on the 
statically linked one to see if it outputs "hello world" and returns an exit 
code of 0).

That's it, 150 lines.  Not big or complicated enough to break up.  (Now 
mini-native.sh is twice that size, and I've pondered breaking it up.  But 322 
lines isn't excessive yet, so breaking it up is still probably a net loss of 
understandability.)

Getting back to menuconfig, since it _is_ so central to your design, let's 
look at the menuconfig entries.  I still have 1.3.2 installed here, which 
starts with nine sub-menus, let's go into the first, "paths and misc 
options":

The first three options in the first menu aren't immediately useful to a 
newbie like me:

  [ ] Use obsolete features
  [ ] Try features marked as EXPERIMENTAL (NEW)
  [ ] Debug crosstool-NG (NEW)

I dunno what your obsolete versions are, I don't know what your experimental 
options are, and I dunno what debugging crosstool-ng does.  I am not 
currently qualified to make any decisions about them, because I don't know 
what they actually control.

Looking at the help... the "obsolete features" thing seems useless?  We've 
already got menus to select kernel and gcc versions, this just hides some of 
those versions?  Why?  (Shouldn't it default to the newest stable version?  
If it doesn't, shouldn't it be _obvious_ that the newest stable version is 
probably what you want?)

Marking old versions "deprecated" might make a certain mount of sense.  
marking them obsolete and hiding them, but still having them available, less 
so.

Similarly, the "experimental" one seems useless because when you enable it the 
experimental versions already say "EXPERIMENTAL" in their descriptions 
(wandered around until I found the binutils version choice menu and looked at 
it to be sure).  They're marked anyway, so why is an option to hide them an 
improvement?

As for the third, wasn't there a debug menu?  Why is "Debug crostool-NG" in 
the paths menu?  (Rummage, rummage... Ah, I see, the debug menu is a list of 
packages you might want to build and add to the toolchain.  Ok, sort of makes 
sense.  Still, the third thing a newbie sees going through in order as 
a "very very expert" option.  Moving on...)

  ()  Local tarballs directory (NEW)
  (${CT_TOP_DIR}/targets) Working directory (NEW)
  (${HOME}/x-tools/${CT_TARGET}) Prefix directory (NEW)

Most users aren't going to care where the local tarballs directory is, or the 
working directory.  The "prefix directory" is presumably different from where 
we just installed with --prefix.  I suppose it's nice that you can override 
the defaults, but having it be one of the first questions a user's posed with 
when going through the options in order trying to configure the thing isn't 
really very helpful.  It's not my problem, just _work_.  (I also don't know 
what CT_TOP_DIR and CT_TARGET are, I'd have to go look them up.)

For comparison, my system creates a tarball from the resulting cross compiler, 
and leaves an extracted copy as "build/cross-compiler-$ARCH".  You can put 
them wherever you like, it's not my problem.  They're fully relocatable.

On the build front, if you want one of the directories (such as "build" 
or "packages") to live somewhere else, move it and put a symlink.  I didn't 
bother to document this because I expected people who think of it...

  [*] Remove documentation (NEW)

Nice, and possibly the first question someone who _isn't_ a cross compiler 
toolchain developer (but just wants to build and use the thing) might 
actually be interested in.

Your ./configure still requires you to install makeinfo no matter what this is 
set to.  You have to install the package so this can delete its output?

Wouldn't it be better to group this with a "strip the resulting binaries" 
option, and any other space saving switches?  (I'm just assuming you _have_ 
them, somewhere...)

  [*] Render the toolchain read-only (NEW)

This is something the end user can do fairly easily for themselves, and I'm 
not quite sure what the advantage of doing it is supposed to be anyway.  In 
any case it's an install option, and should probably go with other install 
options, but I personally wouldn't have bothered having this option at all.

  [ ] Force downloads (NEW)

I noticed your build doesn't detect whether or not the tarballs downloaded 
properly.  I hit this the first time I ran crosstool-ng, when I ctrl-c'd out 
of what it was doing when I got no progress indicator for what seemed like an 
unreasonably long time, and on the second attempt it died because the tarball 
it had halfway downloaded didn't extract right.  (Took me a little while to 
figure out how to fix that.)

Forcing re-downloads every build puts unnecessary strain on the mirrors, and 
seems a bit impolite.  (Plus your re-download can time out halfway through if 
the net hiccups.)  But the alternative you've got is your infrastructure 
won't notice corrupted tarballs other than by dying.

What my download.sh script does is check the sha1sum of any existing tarball, 
keep it if it's correct, and automatically redownload it if the file doesn't 
exist or the sha1sum doesn't match.  (That's also a quick and dirty check 
that the mirrors we're downloading from didn't get hacked, but that's just a 
fringe benefit.)

Mine also falls back to a series of mirrors, most notably 
http://impactlinux.com/fwl/mirror (which covers the "but you're not mirroring 
it!" complaints of the FSF in case they ever decide that I'm a commercial 
user not covered by section 3C of GPLv2, and decide to pull a mepis on me.  
Not that this is a primary consideration, but I _do_ offer prebuilt binaries 
of each toolchain for download.)  So if one of the websites is temporarily 
down, or your wget dies halfway through due to a router reboot and the 
resulting binary is truncated so the sha1sum is wrong, it still has a chance 
to get the file without breaking the build.

The mirror list is in the file download.sh in case people want to edit it and 
add their own, and I also have an environment variable you can set, ala:
  PREFERRED_MIRROR=http://impactlinux.com/fwl/mirror

Which will be checked before the initial download location, so if you have a 
local web server on your LAN it can download stuff from there and never go 
out to the net for it.  But you never HAVE to set that variable, and aren't 
required to know it exists.

The takeaway here is I don't like halfway solutions.  If a problem's worth 
fixing, it's worth fixing thoroughly.  Otherwise, don't address it at all and 
let the user deal with it if they care to.

  [ ] Use a proxy (NEW)  --->

Wow, are these still used in 2009?  Ok?  (It just never came up for me...)

  [ ] Use LAN mirror (NEW)  --->

I mentioned PREFERRED_MIRROR above, and the fact that the mirror I setup is 
already in the default list as a fallback.

In the sub-menu this options, why do you have individual selections instead of 
just having 'em provide a URL prefix pointing to the directory in which to 
find the packages in question?  You already know the name of each package 
you're looking for...

  (10) connection timeout (NEW)

This is an implementation detail.  Users should hardly ever care.

My system uses wget instead of curl (because wget is in busybox and curl 
isn't).  The actual invocation in sources/functions.sh line 189 (shell 
function "try_download") is:

    wget -t 2 -T 20 -O "$SRCDIR/$FILENAME" "$1" ||
      (rm "$SRCDIR/$FILENAME"; return 2)

That's 2 attempts to download, timeout of 20 seconds.  (And if wget exits with 
an error, zap the partial download.)

Since it's a shell script, people are free to change those defaults by editing 
the shell script.  It seems uncommon enough to _need_ to do this that making 
a more convenient way to do it didn't seem worth the extra complexity the 
user would be confronted with to configure the thing.  (I.E. if I keep the 
infrastructure as simple as possible, the user should be able to find and 
edit the wget command line more easily than finding and changing a 
configuration option would be.)

As a higher level design issue, It would have been easier for me to implement 
my build system in python than in bash, but the point of doing it in bash is 
it's the exact same set of commands you'd run on the command line, in the 
order you'd run them, to do this yourself by hand.  So to an extent the shell 
scripts act as documentation and a tutorial on how to build cross compilers.  
(And I added a lot of #comments to help out there, because I _expect_ people 
to read the scripts if they care about much more than just grabbing prebuilt 
binary tarballs and using them to cross compile stuff.)

  [ ] Stop after downloading tarballs (NEW)

This seems like it should be a command line option.  It's a bit awkward that 
if you just want to download the tarballs, you go into menuconfig, switch 
this on, run the build, go back into menuconfig, and switch this off again.

Mine just has "./download.sh", which you can run by itself directly.

  [ ] Force extractions (NEW)

Ah, you cache the results of tarball extraction too.  I hadn't noticed.  (I 
hadn't bothered to mention that mine's doing it because it's just an 
implementation detail.)

This is one of the things my setupfor function does: it extracts source into 
build/sources, in a subdirectory with the same name as the package.  Then 
when it needs to actually build a package, it creates a directory full of 
hard links (cp -lfR sourcedir targetdir) to the source, which is quick and 
cheap.

(By the way, there's a SNAPSHOT_SYMLINK config option, which if set will do 
a "cp -sfR" instead of "cp -lfR", creating symlinks instead of hard links.  
This is noticeably slower and consumes a zillion inodes, which hard links 
don't.  But the _advantage_ of this is your build/sources directory can be a 
symlink to a different filesystem than the one you're building on, possibly 
on something crazy like NFS.  So your extracted source code doesn't have to 
live on your build machine, which is nice if you're building on strange 
little mips or arm systems that have network access but only a ramfs for 
local storage.  Note that building on NFS sucks because the dentry cacheing 
screws up the timestamp granularity make depends on, but having your source 
code _symlinked_ from NFS while the filesystem you're creating all your temp 
files in is local does not have such problems.  The source remains "reliably 
old enough" (unless you're build is crazy enough to modify its source code, 
which should never happen).)

Oh, and the directory it saves under build/sources is just the package name, 
minus the version number.  (The trick to removing the version number is to 
extract it into an empty directory, and then "mv * name-you-expect".  That 
breaks if the package creates more than one directory, but you _want_ the 
script to stop if that happens because something is deeply wrong.)  Removing 
the version number from the cached source directory means that only 
download.sh ever has to care about the version number, the build scripts 
don't.  So usually all you have to do to upgrade a package is change its 
entry in download.sh and rerun the build.  (Admittedly, sometimes you have to 
fix things that break because the new version doesn't build the same way the 
previous one did, but for sanely maintained packages that's not an issue the 
majority of the time.  Alas, most of the packages the FSF maintains are 
insane, but I'm using the last GPLv2 releases of gcc, binutils, and make, and 
using bash 2.05b because newer bash is mostly bloat without benefit, so it's 
really uClibc, busybox and the kernel that get upgraded often.)

Again, I detect "good/stale" cached data via sha1sums.  The extract function 
(in sources/functions.sh) writes a file "sha1-for-source.txt" into the source 
directory it extracts and patches.  When it's run again on the same source, 
it first checks the sha1sums in that file against the sha1sum of the package 
tarball and the sha1sum of each patches that was applied to that package.  If 
they all match, then it keeps the existing source and returns success.  If 
they don't match, it does an rm -rf on the old directory (if any), extracts 
the tarball, and applies all the patches to it in order (again, if any).

Note that the sequencing here is important: it doesn't append the sha1sum for 
the tarball to the file until the tarball has successfully extracted, and it 
doesn't append the sha1 for each patch until "patch" has returned success.  
That way if the extract fails for some reason (possibly disk full) the next 
call to extract will be able to tell that it's wrong, and will rm -rf the 
junk and do it again.

Also note that we don't check the _contents_ of the directory, just the 
sha1-for-source.txt file that says we _put_ source we were happy with there.  
If the user comes along and makes temporary tweaks to this source for testing 
purposes, we keep it until they're done testing, at which point they can 
rm -rf that directory.

By the way, my project's equivalent of "make clean" is "rm -rf build", and the 
equivalent of make distclean is "rm -rf build packages".  I believe that's in 
the documentation.html file, but it should probably also be in the README...

Added.

  [*] Override config.{guess,sub} (NEW)

I consider autoconf/automake horrible abominations that have outlived their 
usefulness, and they need to die and be replaced by something else.  (As 
does "make".)  I believe I ranted about that in the OLS compiler bof 
video. :)

I can sort of see this, but it's one of those "you really, really, really need 
to know what you're doing, and you might be better off patching or upgrading 
the package in question instead".

  [ ] Stop after extracting tarballs (NEW)

In my case, you do "./download.sh --extract" which will download and extract 
every downloaded tarball.  If you run it twice, the second time it should 
just confirm a lot of sha1sums and otherwise figure out it has nothing to do.

You can also "./download.sh && ./download.sh --extract" to get all the 
networking stuff out of the way in one go, and then do all the CPU intensive 
stuff.  I like that because sometimes I have intermittent network access on 
my laptop (I.E. about to move somewhere else in five minutes, and that place 
has no net), so getting all the stuff that needs to talk to the network out 
of the way up front is nice.  *shrug*  YMMV.

The reason I made --extract a command line option instead of an environment 
variable is I can't think of a reason you'd ever want to run it twice in a 
row.  Environment variables have the advantage you'd want to set them 
persistently, but in this case, that's pretty much nonsensical.  Command line 
options are designed for "this run only, do this thing differently".

In comparison, having this functionality controlled via menuconfig seems a bit 
awkward to me.

  (1) Number of parallel jobs (NEW)

My sources/includes.sh autodetects the number of processors and sets CPUS.  
You can override it by setting CPUS on the command line.  (I often 
do "CPUS=1 ./build.sh x86_64" when something breaks so I get more 
understandable error messages.)

In general, I try never to ask the user for information I can autodetect sane 
defaults for, I just let them override the defaults if they want to.

  (0) Maximum allowed load (NEW)

Ooh, that's nice, and something mine doesn't have.  Personally I've never had 
a clear enough idea of what loadavg's units were to figure out how it equated 
to slowing down my desktop, and I've actually found that my laptop's 
interactivity going down the drain is almost never due to loadavg, it's due 
to running out of memory and the thing going swap happy with the disk pegged 
as constantaly active.  (The CPU scheduler is way the heck better than the 
I/O scheduler, and virtual memory is conceptually horrible and quite possibly 
_never_ going to be properly fixed at the theoretical level.  You have to 
accurately predict the future in order to do it right, that's slightly 
_worse_ than solving the halting problem...)

  (0) Nice level (NEW)

Again, trivial to do from the command line:

  nice -n 5 ./build.sh

I had some scripts do this a while ago, but took it out again.  I go back and 
forth on whether or not it's worth it.  It would be easy enough for me to add 
this to config and have build.sh call the sub-scripts through nice, so you 
could set this persistently.  I just haven't bothered.  (I have sometimes 
reniced the processes after launching 'em, that's easy enough to do too.)

  [*] Use -pipe (NEW)

Why would you ever bother the user with this?  It's a gcc implementation 
detail, and these days with a modern 2.6 kernel dentry and page caches you 
probably can't even tell the difference in benchmarks because the data never 
actually hits the disk anyway.

Have you actually benchmarked the difference?

  [ ] Use 'ash' as CONFIG_SHELL (NEW)

A) I haven't got /bin/ash installed.  Presumablly you need to install it since 
the help says it's calling it from an absolute path?

B) If your scripts are so slow that you need a faster shell to run them, 
possibly the problem is with the scripts rather than with the shell?

I admit that one of the potential weaknesses of my current system is that it 
calls #!/bin/bash instead of #!/bin/sh.  I agonized over that one a bit.  But 
I stayed with bash because A) dash is seriously broken, B) bash has been the 
default shell of Linux since before the 0.0.1 release.

If you read Linus's book "Just for fun", he details how he wrote a terminal 
program in assembly that ran booted from a floppy because minix's microkernel 
serial port handling couldn't keep up with a 2400 bps modem without dropping 
characters, and then he taught it to read/write the minix filesystem so he 
could upload and download stuff, and then he accidentally turned it into a 
unix kernel by teaching it to handle all the system calls bash needed so he 
could rm/mv/mkdir to make space for his downloading without having to reboot 
into minix.  The shell was specifically bash.  Redirecting the /bin/sh 
symlink of ubuntu to something other than bash was the DUMBEST TECHNICAL 
DECISION UBUNTU HAS EVER MADE.  (Note that they still install bash by 
default.)

Anyway, if your build scripts really are that slow you can autodetect 
when /bin/dash or /bin/ash exists on the host and use those if they're there.  
But personally I'd recommend making your build scripts do less work instead.

  Maximum log level to see: (INFO)  --->

I don't have a decent idea of what you get with each of these.  (Yes, I've 
read the help.)

In my build, it spits out all the output you get from the build, but you're 
welcome to redirect it using normal unix command line stuff.  I usually do

  ./build.sh 2>&1 | tee out.txt

And another trick is that each new section and package is announced with a 
line starting with ===, so you can do:

./build.sh 2>&1 | grep ===

Or drop the 2>&1 part to see stderr messages as well.

Again, I gave them the output, and a couple of hooks to make wading through it 
easier, but what they do with it is not really my problem.

Oh, I pull one other dirty trick, at the end of setupfor:

  # Change window title bar to package now
  echo -en "\033]2;$ARCH_NAME $STAGE_NAME $PACKAGE\007" 

So you can see in the window title bar what architecture, stage, and package 
it's currently building.

  [ ] Warnings from the tools' builds (NEW)

Again, filtering the output of the build I leave to the user.  They're better 
at it, and 90% of the time they just want to know that it's still going, or 
that it succeeded, or what error it died with.

But I can only _guess_ what they want, so I don't.  In general, I try not to 
assume they're not going to want to do some insane crazy thing I never 
thought of, because usually I'm the one doing the insane crazy things the 
people who wrote the stuff I'm using never thought of, so I sympathize.

  [*] Progress bar (NEW)

I have the "dotprogress" function I use for extracting tarballs, prints a 
period every 25 lines of input.

In general I've found "crud is still scrolling by in the window" to be a 
decent indication that it's not dead yet.  I mostly stay out of these kind of 
cosmetic issues these days.

I used to change the color of the output so you could see at a glance what 
stage it was, but people complained and I switched to changing the title bar 
instead.  Tells you at a glance where the build is, which was the point.  
(You can still get the colors back with a config variable, but gnome can't 
give you a real black background, just dark grey, and less than half as many 
colors easy to read on a white background.)

  [*] Log to a file (NEW)

Again, "./build.sh 2>&1 | tee out.txt".  Pretty much programming 101 these 
days, if you haven't learned that for building all the other source packages 
out there, cross compiling probably isn't something you're ready for.

  [*]   Compress the log file (NEW)

./build.sh 2>&1 | tee >(gzip > out.txt)

And I'm at the end of this menu, so I'll pause here for now.  (And you were 
apologizing for writing a long message... :)

Rob
-- 
GPLv3 is to GPLv2 what Attack of the Clones is to The Empire Strikes Back.

--
For unsubscribe information see http://sourceware.org/lists.html#faq


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]