The experimental JUCER and "JUCE_ROOT"


#1

Continuing my thoughts about builds and the experimental JUCER…

I have this monstrosity in my experimental JUCER right now:

This sort of thing is narsty and hard to maintain and was fairly difficult to arrive at in the first place.

So let me propose a new concept - the concept of JUCE_ROOT. This is the directory that contains the juce directory - and is used as a default for all relative paths. That line above would become:

isn’t that nicer - but also, easier to come up with in the first place?

Right now, you have the “Juce Location” variable - which is fine - that can be used to extract the JUCE_ROOT by going up one level. But in practice you also end up encoding “Juce Location” into all your header and library paths when you link.

And so you can guess that I like this JUCE_ROOT idea because it’ll also make third-party package management easy a little later down the road.


#2

Well, that is a bit nasty, but if all paths were relative to the juce folder, you’d need to hard-code the paths to any folders inside your project, and wouldn’t be able to move the project’s position relative to the juce folder without updating all the includes. I’d find that a bit annoying…

What might be a better solution would be for me to provide some strings that will be replaced, so it’d become

BTW the idea is that you don’t need to have the juce folder in your include path, as JuceHeader.h will pull in everything you need… Are you including some files directly for dastardly reasons?


#3

But that’s a problem now, anyway - if you move your project’s position, you STILL have to update all the includes. In fact, that’s pretty well always a problem. Try moving your XCode project into a subdirectory… :frowning:

but…

YES, ten times better! DO IT DO IT DO IT! (or, I might do it - see below…)

Comments…

  1. make the expansion syntax accept either XXX or {XXX} - or if you can only do one, do the second. There’s inevitably the day you want to say $FOO_bak where your variable is FOO.
  2. I think you could simply add this facility to EVERY text string in the JUCER for not-so-much-more work - you’d simply derive from TextPropertyComponent and just expand the template before return the value…!
  3. And if you’re going to do that, why not add another line to the project/config: “Template variable assignments” - so I can just set my own template variable here, and then use it in my various assignments later? You could use a format just like for symbol definitions to the compiler.

So I’ve actually been fiddling with the Experimental JUCER (it’s experimental, right, so I get to experiment with it??) and might send you, Jules, some changes, but let me tell you what I’m thinking.

If you take the suggestion above, there are at least four fields that can really be defined at EITHER the project level OR the build level. These fields are extra linker flags, extra compiler flags, header include paths and template variable assignments.

In fact, IMHO the way linker and header flags are put right now is exactly backward. So far, all my header include paths are the same for all (well, both) my configurations, so I have to copy that field exactly in two places. On the other hand, the fact that there’s only one field for the extra linker flags over all configurations means that there is no way to link to debug libraries in debug and release libraries in release.

My solution to this would be to include these four fields, and perhaps others, in three places in the JUCER - one at the project level, one at the configuration level, and one at the “target” level (I’m not sure what you call the “Mac OS vs Windows vs Linux vs…” level). There would be a set of default variables (e.g. JUCE_HOME), then the project level could override those and the configuration level could override those and finally the “target” level would override those.

Using my current case as an example, which I believe is pretty generic, I’d set all my include file paths in the project. I have debug and release versions of the libraries I use, so in the e.g. Debug configuration level I’d set the extra linker flag -L$JUCE_ROOT/libfoo/Debug - but I’d set the actual library names themselves in the project: -lfoo

(Actually, there would probably be some variable indicating the configuration so you could bring up that extra linker flag to be -L$JUCE_ROOT/libfoo/$CONFIG and define it, too at the project level…)

Oops!

Ah, this clears up a lot of things. I’ll just get rid of the direct includes…

but I do feel fairly strongly that including everything is not a good idea - I put in the direct includes not just because it’s simply very useful for documentary purposes, but because in a world where you “include [exactly] what you use”, this results in decreased compilation times (though I do understand that your headers are pre-compiled).

All those macros that control the inclusion of parts of your system would simply be unneeded if you only included what you needed - and if you used forward references for any classes that are mentioned in headers but not exposed, you’d get an even smaller compile time, but also, a system with parts where software breakage is much more confined. (I do understand that there are serious historical reasons for the universal include and that backward compatibility trumps all…)

Once you’re simply “including what you use” you have a lot more flexibility to use templates and inline functions without killing your compilation time, and the optimizer can have a field day.

This is definitely the Google heritage speaking - “include what you use” became very important as the system got bigger - but I’ve kept that where I’ve dropped other Google things because I find it so clear and useful. I think it’s no coincidence that my two other primary languages, Java and Python, both require “include what you use” (and Java essentially enforces the “exactly” as it’s a compiler warning you can “make go away” with a keystroke).


#4

yes. I’m sold on that idea. It’s actually quite complicated to implement, because these constants will have different meanings depending on the context in which they’re used - e.g. when generating each type of project file, $JUCE will have a different value depending on the platform and settings, so they have to be replaced only at the last moment.

I’ve also been pondering on the way the configs are structured, but can’t quite decide on the best design. The best I can come up with so far would be that you have the normal list of global settings, plus the list of exporter targets (i.e. xcode, msvc, etc) which inherit the global properties and can optionally override them, as well as adding their own settings. Then, inside each exporter target, you also have a set of config types (i.e. debug/release, etc), which inherit all the parent settings and can also override values if they want to. This would be the most powerful way to do it, and would allow things like generating different types of project (e.g. exe and plugin) within the same project, but I think it could be a bit confusing. Any thoughts on the matter are welcome!

Re: “including just what you need”: I agree with that in the context of a project’s own files - i.e. I think that a file should directly include any headers that are in its own project. But when including external headers, I prefer to have them all cleanly tucked away inside a wrapper. Any small slowdown in compilation is more than compensated by the time you don’t have to spend finding the headers that you need and writing and maintaining all those pesky include statements. Compilers are built to cope with every file containing a line like #include <windows.h> or #include <Cocoa/Cocoa.h>, so including juce.h should be a doddle for them.


#5

platform

The word I was looking for!

A build is defined by the project, the platform and the configuration. Each of these three parts has a variable assignment.

complicated to implement

I’d be happy to knock the variable substitution part out, it’s right up my alley. In fact, I’ll start scribbling on it right now…

You represent a variable assignment as a StringPairArray. There’s a function to “add” two variable assignments, with the second one trumping the first. [EDIT: you have this as StringPairArray::addArray] Then you simply add the project, platform and configuration’s variable assignments together and substitute it into a string - which last I could write pretty fast (you don’t seem to have formatting like this in your code base…)

I’ve also been pondering on the way the configs are structured.

I think if you allow this hierarchical variable specification, it’ll do any case you need with one tool.

the time you don’t have to spend finding the headers that you need and writing and maintaining all those pesky include statements.

That’s fine if you’re the only one ever reading your code - and if you know the source that the code is referring to. In fact, code is read far more often than it is written, and often by people who are not as familiar with the source material as you are - perhaps even you sometime later when your memory has faded…

My hope is that you should be able to read my source code - so that you could look up any symbols you didn’t understand in my code by going to the file referenced in the header. If my file of source code has so much functionality that this list of include files is very long, well, the source file is too long.

I have a preference toward small source code files - or at least as small as possible, sometimes you need a lot of code to implement one single function! It should be very rare that your file depends on so many other concepts that there are a lot of header files.

Again, I direct you to this great article which changed my life (slowly - I had to think about it for a long time).

Now, I rarely use classes unless I need to dispatch using a virtual method - and then, often, I have a class just to do the dispatching. Instead, I use structs with perhaps a few utility methods, and then separate functions that operate on these structs.

Having structs and functions that operate on those structs is far less code, at least partly because I then just pass those structs around like they’re primitives because they have no “heavy state” associated with them.

There’s a certain amount of redundant copying, but the return value optimization takes care of nearly all of it, and, frankly, the cost of copying your control structures is almost never the issue.


#6

I think it’d need to be done using a ValueTree, because they need to be connected up to PropertyComponents for editing. They’d also need to use custom ValueSources for their values, so that a non-overridden value has a connection to its inherited value, and can display that value when it’s not overridden. Don’t worry, I’ll get working on this asap, it needs sorting out!

Yep, I’ve read that article before, and I do totally understand and agree with all the points, though I still can’t help liking member functions for a couple of reasons:

  • they’re easy to find when you have an object and need to know how to do a particular operation to it. You can just go to the class’s documentation, and there are all its methods, easy to scan through.
  • I prefer the syntax of writing a method call. To me, myString.toUpperCase().reversed().substring (2).getValue() reads much better than valueOf (substring (reversed (toUpperCase (myString)), 2)). And if you start mixing up method calls and function calls in a big expression, it’s even more jumbled!

I reckon a better solution would be to be able to declare a method as a “non-friend”, so that it would basically have the same privileges as a non-member, non-friend, but would still be there in the class declaration, and would still use the method call syntax…


#7

Here’s code to format variables…

[code]inline String substituteVariables(const StringPairArray& variables, const String& format) {
String result;
const char *begin = format.toCString();
const char *end = begin + format.length() + 1; // Including the \0!
const char *consumed = begin;

bool escaped = false;
bool evaluating = false;
int brackets = 0;  // Number of levels of brackets in evaluating - only 0 or 1.

for (const char *i = begin; i != end ; ++i)
{
    if (!escaped)
    {
        if (*i == '\\')
        {
            escaped = true;
            continue;
        }

        if (evaluating)
        {
            if (*i == '{')
              brackets = 1;

            if (brackets ? (*i == '}') : (!isalnum(*i) && (*i != '_')))
            {
                String name = String(consumed + brackets, i - consumed - brackets);
                result += variables.getValue(name, "");
                evaluating = false;
                consumed = i + brackets;
            }
        }
        else if (*i == '$' || *i == '\0')
        {
            result += String(consumed, i - consumed);
            evaluating = true;
            consumed = i + 1;
        }
    }
}
return result;

}

#endif // REC_UTIL_SUBSTITUTE
[/code]

and here’s a test…

[code]TEST(Util, Mod) {
StringPairArray v;
EXPECT_STREQ(substituteVariables(v, “”).toCString(),
"");

const char* pattern = "Reporter $alias is really ${real name}.";
EXPECT_STREQ(substituteVariables(v, pattern).toCString(),
             "Reporter  is really .");

v.set("real name", "Superman");
v.set("alias", "Clark Kent");

EXPECT_STREQ(substituteVariables(v, pattern).toCString(),
             "Reporter Clark Kent is really Superman.");

}
[/code]