Coder Social home page Coder Social logo

nonrec-make's Introduction

################################################################################
#                  Non-recursive make build system                             #
#                  -------------------------------                             #
#     Copyright (C) 2012 Andrzej Ostruszka <[email protected]>       #
#                                                                              #
#          URL: http://github.com/aostruszka/nonrec-make                       #
#          (or older: http://nonrec-make.googlecode.com/)                      #
#                                                                              #
# Permission is hereby granted, free of charge, to any person obtaining a copy #
# of this software and associated documentation files (the "Software"), to     #
# deal in the Software without restriction, including without limitation the   #
# rights to use, copy, modify, merge, publish, distribute, sublicense,         #
# and/or sell copies of the Software, and to permit persons to whom the        #
# Software is furnished to do so, subject to the following conditions:         #
#                                                                              #
# The above copyright notice and this permission notice shall be included in   #
# all copies or substantial portions of the Software.                          #
#                                                                              #
# Except as contained in this notice, the name(s) of the above copyright       #
# holders shall not be used in advertising or otherwise to promote the sale,   #
# use or other dealings in this Software without prior written authorization.  #
#                                                                              #
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR   #
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,     #
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE  #
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER       #
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING      #
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS #
# IN THE SOFTWARE.                                                             #
################################################################################

NOTE: This readme _might_ not be up to date.  For up to date information
see the above URL (and accompanying wiki pages).

This is my attempt to implement a non-recursive make build system.  For
the motivation Google for the paper "Recursive make consider harmful" by
Peter Miller.

I've seen couple of other proposals and decided to have something that
will be a blend of nice ideas I have seen plus some improvements.  If
you actually use this I'd like to hear from you :) - is it useful, does
it perform well, do you have any suggestions for the improvements ...
and so on.  This implementation is based on GNU make and its new
features introduced in 3.80.  But don't use that version - these
features had bugs in that version.  Use version 3.81 where everything
works OK.

Before you keep on reading though, just take a look at the structure of
the Rules.mk files (Rules.top has exactly the same structure as Rules.mk
- it just has another name to ease location of the top level project
directory from its subfolders).
I've got a feeling that it is much easier to understand how the system
looks from the user perspective just by looking at the example than
reading its explanation :D.

OK, now that you have a feeling how the Rules.mk look like let me walk
you through an example (ex1 in the repository).  Consider the project
that has some source files at the top directory and is depending on two
libraries in Dir_1 and Dir_2 and another one in Dir_3.  The libraries
themselves are partitioned between several subdirectories and Dir_2 has
some examples in a separate subfolder (do not pay attention to all *.c
files).

ex1/
  Makefile
  Rules.top <- Just a symlink to Rules.mk to mark where the top level is
  Rules.mk
  main.c
  top_a.c
  top_b.c
  cli.c
  cli_dep.c
  mk/* <- This is where the mk files from this build system are
  Dir_1/
    Makefile
    Rules.mk
    dir_1_file1.c
    dir_1_file2.c
    dir_1_file3.c
    Dir_1a/
      Makefile
      Rules.mk
      dir_1a_file1.c
      dir_1a_file2.c
      dir_1a_file3.c
    Dir_1b/
      Makefile
      Rules.mk
      src/
        dir_1b_file1.c
        dir_1b_file2.c
  Dir_2/
    Makefile
    Rules.mk
    dir_2_file1.c
    dir_2_file2.c
    Dir_2a/
      Makefile
      Rules.mk
      dir_2a_file1.c
      dir_2a_file2.c
      dir_2a_file3.c
    Dir_2b/
      Makefile
      Rules.mk
      dir_2b_file1.c
      dir_2b_file2.c
      dir_2b_file3.c
    Dir_ex/
      Makefile
      Rules.mk
      ex.c
  Dir_3/
    Makefile
    Rules.mk
    dir_3_file1.c
    dir_3_file2.c

There's one top level make file (Rules.top) which eventually includes
all the makefiles.  In addition in each directory there is Makefile
(which can be a link to the one in the top level directory) which
searches for the Rules.top includes it with the default goal
changed to rebuild only targets for the current directory.  This allows
you to run make from each subdirectory and update only part of the
targets (in contrary to other implementation which usually require
you to run it at the top level and make full build each time).

This build system was designed to have very simple structure of the
"user makefiles".  The user just has to set the Rules.mk files in each
directory and some general configuration options.  All the "magic" is
hidden in header.mk, footer.mk and skel.mk which don't have to be
modified [1].

The structure of the Rules.mk is following (this is from top level
Rules.top which has the same format as Rules.mk - and in fact it is
suggested that it should be a symlink to normal Rules.mk file since it
will allow for this project to act as a subproject of some super project
treating your whole project tree as a subdirectory[2]):

-8<---Rules.top---------------
1:  TARGETS = app.exe cli.exe
2:  SUBDIRS = Dir_1 Dir_2
3:
4:  app.exe_DEPS = top_a.o top_b.o main.o $(SUBDIRS_TGTS)
5:  app.exe_LIBS = -lm
6:  # Let's use DEFAULT_MAKECMD for app.exe
7:
8:  cli.exe_DEPS = cli.o cli_dep.o
9:  cli.exe_CMD = $(LINK.c) $^ $(LDLIBS) -o $@
-8<---------------------------

Line 1 - this directory has two targets that should be built.
Line 2 - this directory has two subdirectories that should be scanned
Line 4 - app.exe depends on ... (SUBDIRS_TGTS is a variable that
	 contains all the targets from the subdirectories mentioned at
	 line 4)
Line 5 - app.exe should be linked with math library
Line 6 - app.exe will be built with default "rule"
Line 8 - cli.exe depends on ... and
Line 9 - use the following command to build it

You can specify the targets for current directory in two ways:
1. Give them in TARGETS.  Each target can have it's own *_DEPS, *_LIBS
  and *_CMD which give the dependencies, additional libs needed and
  a command to run which will update the target.  They are explained
  a bit below.
2. The targets are simply objects - or in more general files that
  match patterns in AUTO_TGTS, and have appropriate rules in 'skeleton'.
  In that case you can list them in OBJS or SRCS like e.g. in Rules.mk
  from Dir_1a

-8<---Dir_2/Dir_2a/Rules.mk---
1: SRCS := dir_2a_file1.c dir_2a_file2.c dir_2a_file3.c
-8<---------------------------

There are "reserved" variables that you should not modify.  Most notably:
- $(d) is the directory of the current Rules.mk [see note 2]
- $(TOP) is the top level directory of the project tree
- $(MK) is the directory where the included *.mk makefiles are
For the full list you have to take a look at the makefiles in mk
directory (e.g. in the skel.mk there are macros 'include_subdir_rules',
'save_vars', 'tgt_rule' and 'skeleton' which you should not change [1]).

Going back to the Rules.mk.  Normally wildcards in variable assignments
are not expanded in make but this make system detects wildcards in SRCS
and expands them (both in directory of the Rules.mk and its SRCS_VPATH
subdirectories - see below what SRCS_VPATH is used for).  Thus you can
simply say in Rules.mk:

SRCS := *.c

If you have directory with large number of files where simple glob is
what you want to use in SRCS but there are some files that you'd like to
exclude just list them in SRCS_EXCLUDES :) - this is a list of makefile
patterns e.g.

SRCS_EXCLUDES := extra% test%

Of course you can use the built in make wildcards but you should do that
as follows:

SRCS := $(notdir $(wildcard $(d)/*.c))

Keep in mind that the directory where make is invoked is usually different
from where given Rules.mk is located.

When supplying the value for *_DEPS you can refer to the object from the
same directory with no directory prefix.  To be specific all
dependencies that are not absolute paths will be treated as ones from
the $(OBJDIR) subdirectory of current directory (OBJDIR and OBJPATH are
"discussed" below).  You can use SUBDIRS_TGTS variable which will list
all targets in subdirectories.  You can also name them explicitly like
$(TARGETS_$(d)/subdir) and so on - see e.g. the Rules.mk in Dir_2
directory where Dir_ex is mentioned as a subdirectory but is excluded
from the *_DEPS (this allows you to create folders that "inherit" all
the setting from the project build system but are not meant to be a part
of the project itself - like examples).  For instance:

dir1_lib.a_DEPS = dir_1_file1.o dir_1_file2.o dir_1_file3.o $(SUBDIRS_TGTS)

tells that dir1_lib.a (which will be created in Dir_1/$(OBJDIR)) depends
on several object files from the same directory and the targets from all
subdirectories.

One last thing about the TARGETS/OBJS.  By default source files for the
objects are searched in the directory where Rules.mk is, but if you want
to have source files in a subdirectory (say 'src') you can do that via
SRCS_VPATH variable (see skel.mk).  E.g.:

SRCS_VPATH := src1 src2

will cause make to first look at the directory where Rules.mk is present
and then in its src1 and src2 subdirectories.

*_LIBS are appended to the LDLIBS variable when updating the target.
This variable is used by several make built in rules but if you create
your own rule or MAKECMD.* (see next paragraph) you can refer to it (see
the function 'save_vars').

When *_CMD is not present and the target does not match any pattern in
AUTO_TGTS then either MAKECMD.suff (where .suff is the suffix of the
target) or DEFAULT_MAKECMD is used - take a look into skel.mk.

If you want to setup special flags for compilation you can do that via
"directory specific variables".  As an example here's what I did for
compilation of C files.  There's a built in rule in make which uses
COMPILE.c variable for making %.o out of %.c so I added $(DIR_CFLAGS) to
its default value and DIR_CFLAGS is defined in skel.mk as:

DIR_INCLUDES = $(addprefix -I,$(INCLUDES_$(<D)))
DIR_CFLAGS = $(CFLAGS_$(<D)) $(DIR_INCLUDES)

So it extracts the directory part of the first prerequisite in the rule
(that is %.c file - check the section 'automatic variables' in make
manual for the meaning of $(<) and $(<D)) and refer to variables named
CFLAGS_the_directory_part and INCLUDES_the_directory_part.
Thus if you wanted to add special includes for files in Dir_1/Dir_1b you
could add:

INCLUDES_$(d) := $(TOP)/some/special/include_dir

into its Rules.mk and all files in this directory will be compiled with
-I$(TOP)/some...  switch.  The same goes for CFLAGS and CXXFLAGS.

The same goes for the linker flags - quoting from skel.mk:

LDFLAGS = $(addprefix -L,$(LIBDIRS_$(subst /$(OBJDIR),,$(@D))))
LDLIBS = $(LIBS_$(@))

The above means that if targets in given directory need to be linked
with special -L switches you can provide them via LIBDIRS_$(d)
variables.  If there are some global -L switches just append them in
skel.mk.  The second line above shows how *_LIBS variable that you can
give for specific target gets added to the LDLIBS (there's 'save_vars'
in between if you're curious :)).

You can of course use target specific variables that GNU make supports
so you have more control (if you don't know what target specific
variables are take a look into manual).  Say you want to compile
dir_1b_file2.c with an additional flag but all other files in
Dir_1/Dir_1b directory should not have this flag turned on.  All you
need to do is to add this line into Rules.mk in Dir_1b directory.

$(OBJPATH)/dir_1b_file2.o : CFLAGS += -ansi

OBJPATH is a variable that contains the full directory where the
resulting object file will be placed.  While we are at this, by default
all targets are compiled into OBJDIR (defined in skel.mk as 'obj')
subdirectory of the directory where Rules.mk is present.  You can use
this OBJDIR variable (and perhaps some conditional statements) to setup
the output path according to your current compilation mode.  E.g.
obj/debug for objects with debugging information or obj/ppc_7xx for
cross compilation to the given Power PC family and so on.  There's
predefined (in config* files) HOST_ARCH variable that you can use for
this (e.g. set OBJDIR := obj/$(HOST_ARCH) in skel.mk).

Finally let me explain what targets are defined and the way you can run
them from command line.  By default (that is if you have not modified
anything :)) there are several targets that are "global".  It is 'all',
'clean_all' and 'dist_clean'.  If you specify them in the command line
they will respectively rebuild whole tree, clean everything and clean
everything together with the removal of OBJDIRs - no matter from which
directory you started make.  BTW if there's something that you want to
clean up and it's not in the OBJDIR - e.g. you've got file lexer.l out
of which lexer.c and lexer.h is generated and you want them to be
removed - you can specify this in the variable CLEAN (this is relative
to the directory where Rules.mk is).
In addition to those each dir has "it's own" targets.  These are:

1) dir_<directory_path>
   which builds all targets in given directory plus its dependencies
2) tree_<directory_path>
   which builds all targets in subtree starting at directory given
3) clean_<directory_path>
   which removes all "products" from the $(OBJDIR) subdirectory of
   current directory
4) clean_tree_<directory_path>
   which does clean_<directory_path> and the same for each its
   subdirectory

For your convenience there are couple "aliases" defined (see Makefile).
When no target is given on command line it defaults to dir_$(pwd).
If you give 'clean' as a target that will result in execution of target
clean_$(pwd) and the same for 'clean_tree'.  E.g. say you're in Dir_1.
Then:

* 'make' (same as 'make dir_$(pwd)')
   builds all targets in the Dir_1 which in our example is
   Dir_1/obj/dir1_lib.a - of course any of its dependencies that are not
   up to date are updated also.  This rule has one exception - if your
   Rules.mk has no targets and only SUBDIRS (e.g. you have grouped
   several subdirectories in one directory) then simple 'make' in this
   directory - instead of doing nothing - will build targets of all its
   subdirectories.
* 'make tree' (same as 'make tree_$(pwd)')
   rebuilds everything in given subtree
* 'make clean' (same as 'make clean_$(pwd)')
   removes everything from Dir_1/obj/
* 'make clean_tree (same as 'make clean_tree_$(pwd)')
   cleans Dir_1/obj and Dir_1/Dir_1[abc]/obj

You can obviously provide the path by yourself - it does not have to
be $(pwd) - and as usual you can build particular object files too e.g.
'make $(pwd)/obj/dir_1_file1.o'

And that would be it.  Gee, so much writing for something that is rather
simple to use - go ahead and take a look again at these Rules.mk in
various directories.  Setting up you project should be simple by now :).

Have fun!

					Andrzej Ostruszka

[1] Unless this build system does not do what you wanted :-P.  In that
case you probably need to spiff it up.  So you'll need to
digest it first and note [3] is my hand at it :).

[2] There is one limitation that you should be aware.
Prior to commit 070f681 you should not have in your project two "target
specific" LIBS, LDFLAGS or CMD variables (that is those that are used
during second phase of make execution) that have the same names!  For
example in one part of your tree you're generating target abc which has
it's abc_CMD and in the other part another target that has the same name
and also it's own command.  In such case the last assignment to abc_CMD
will hold and it will be used for both targets.  The same goes for LIBS
and LDFLAGS.

And this also applied to situation when you would like to use two
subprojects in one larger project.  There should be no clash between
variables from these subprojects.

Starting with commit 070f681 you can have in your (sub)projects two
LIBS, LDFLAGS or CMD variables.  However there is a catch here! Since
these variables are not mandatory (you don't have to provide them for
each target) in order to differentiate between case where abc_CMD was
actually given for this instance of abc target from the situation where
abc_CMD is still visible from previous instance of abc target those
abc_(LIBS|LDFLAGS|CMD) variables are cleared once their value is
used/remembered (see save_vars macro in skel.mk). That means referring
to these variables outside Rules.mk where they were assigned will not
work (and even in the same Rules.mk they will work only in case of
simply expanded variables - not recursive). If you have such need I'd
advice to introduce your own variable and use this variable in all
places, e.g.:

MATH_LIBS := -lgsl -lgslcblas -lm
...
TARGETS := abc

abc_DEPS = ...
abc_LIBS = $(MATH_LIBS)
...

[3] You should know that make works in two phases - first it scans the
makefiles and then it begins their execution (see discussion of
'immediate' and 'deferred' in section 'Reading Makefiles' of make
manual).  This implies that the $(d) variable is not valid during
execution of the commands used for target updates.  If you need to refer
to the directory of the target or prerequisite you should rely on
automatic variables (@, <, ...) and built in functions (dir, notdir,
...).

Every Rules.mk (or Rules.top) need to be included in cooperation with
header.mk and footer.mk.  This is now done automatically but in older
versions this was not so and user had to include them manually.
The main purpose of header is to clear all variables that you can use
inside Rules.mk so that values set in one rules file does not propagate
to other.  In addition at the top level it sets the $(d) variable (which
stands for the directory where the currently included Rules.mk is) and
includes the skel.mk so the top level Rules.top can have exactly the
same structure as other Rules.mk.

The skel.mk is a skeleton which defines variables used by make.  In
addition it includes:
- config.mk - this is a file with the "configuration" for your project
- def_rules.mk - where to put general rules (e.g. pattern rules).  E.g.
  if you want to add a rule that builds %.o out of %.m4 (by running m4
  preprocessor before passing the contents of file to CC) just put it in
  here.

skel.mk also defines some functions like save_vars and tgt_rule
which are called in the footer.mk.  Take a look into make manual for the
way the functions are defined and called if you're not familiar with it.

include_subdir_rules: This is where I keep track of directory of
	currently included makefile and include the Rules.mk from the
	subdirectories.  This function is called in footer.mk in foreach
	loop with a subdirectory name as an argument.

save_vars: This one is very simple it just saves the target specific
	variables under their "full path" variables.  E.g.
	dir1_lib.a_DEPS will get saved as DEPS_$(TOP)/Dir_1/obj/dir1_lib.a
	This has two purposes.  First it allows you to have targets with
	the same names in different directories (not that I recommend
	this :)) and allows for easier definition of tgt_rules :-P

tgt_rule: This is where the rule for given target is created.  First
	I convert all relative dependencies to the absolute ones then
	I include all dependency files (by default they are created as
	side effects during compilation - if your compiler does not
	allow you to do that just make simple shell script that will do
	this).  I also append appropriate libs and then issue the rule
	for this target.  By using the short circuiting 'or' command
	I give priority to the CMD over MAKECMD.suffix which itself has
	priority over DEFAULT_MAKECMD.

skeleton: This is just a skeleton for compilation of files in given
	directory.  If you want to add some rule here then also add
	appropriate pattern to the AUTO_TGTS that will filter out these
	targets and prevent generation of the specific rules via
	tgt_rule function.

The footer.mk is where Rules.mk gets translated into the proper
makefile.  There's not much to explain, just take a look in there.
First I memorize the targets for given directory (either from OBJS/SRCS
or from TARGETS) with appropriate directory prefix.  If there's need to
save the target specific *_(CMD|DEP|LIB) I do it.  Then I include all
the Rules.mk from the subdirectories.  Then I define the targets for
given directory either explicitly (like clean_all or clean_$(d)) or by
evaluation of the 'skeleton' mentioned above or by iterating through all
targets which do not match AUTO_TGTS and evaluating what tgt_rule
function for this target has returned.

In case you want to play with these settings make sure you understand
how make works, what are it's phases, when and how the variables are
expanded and so on.  It will save you a lot of time :).

Best regards
Andrzej

nonrec-make's People

Contributors

aostruszka avatar kholdstare avatar torpesco avatar willow-ahrens avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

nonrec-make's Issues

Can I compile exe and so in one directory hierarchy both?

My hierarchy is just as below.
/ex
dir_1/
 dir_2/
 dir_3/
 dir_4/
  dir_4a/
I want to build an exec and a library. The exec depends on the C files under dir_1/2/3. The library only depens on the C files under dir_4a.

So how to define the DEPS about the exec? $(SUBDIRS_TGTS) seems not work well, because it will to find the library, which I do not want it to.

Idea: Use order-only prerequisites for $(OBJPATH)

I made a change in nonrec-make to match what we'd done in another project that specifies different directories for build output, following the GNU Make manual: http://www.gnu.org/software/make/manual/make.html#Prerequisite-Types

All references to $(OBJPATH)/.fake_file were changed to just $(OBJPATH), and the rule to create the directory was changed to a simple mkdir -p:

diff --git a/mk/def_rules.mk b/mk/def_rules.mk
index 54b0794..d1c7368 100644
--- a/mk/def_rules.mk
+++ b/mk/def_rules.mk
@@ -61,10 +54,9 @@ LINK.cc = $(call echo_cmd,LINK $@) $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $(T
 %.CPPFLAGS : %
        @echo $(CPPFLAGS)

-# In this build system all objects are in a separate directory and
-# I make sure this directory exists by the dependency on this fake file
-%/$(OBJDIR)/.fake_file:
-       @[ -d $(dir $@) ] || mkdir -p $(dir $@); touch $@
+# Create the output directory for build targets.
+%/$(OBJDIR):
+       @mkdir -p $@

 # Generic rules.  Again, since the output is in different directory than
 # source files I cannot count on the built in make rules.  So I keep

Dependencies were changed to order-only:

 define skeleton
-$(OBJPATH)/%.o: $(1)/%.cpp $(OBJPATH)/.fake_file
+$(OBJPATH)/%.o: $(1)/%.cpp | $(OBJPATH)
        $(value COMPILECMD)

-$(OBJPATH)/%.o: $(1)/%.cc $(OBJPATH)/.fake_file
+$(OBJPATH)/%.o: $(1)/%.cc | $(OBJPATH)
        $(value COMPILECMD)

-$(OBJPATH)/%.o: $(1)/%.c $(OBJPATH)/.fake_file
+$(OBJPATH)/%.o: $(1)/%.c | $(OBJPATH)
        $(value COMPILECMD)

...and in skel.mk's tgt_rule:

-$(1): $$(abs_deps) $(if $(findstring $(OBJDIR),$(1)),$(OBJPATH)/.fake_file,)
+$(1): $$(abs_deps) $(if $(findstring $(OBJDIR),$(1)),| $(OBJPATH),)

In footer.mk, the clean target removes $(OBJPATH):

@@ -77,7 +82,7 @@ clean_$(d) :
 else
 clean_$(d) : clean_extra_$(d)
 endif
-       rm -f $(TOP_BUILD_DIR)$(subst clean_,,$@)/$(OBJDIR)/*
+       rm -rf $(TOP_BUILD_DIR)$(subst clean_,,$@)/$(OBJDIR)

 # clean_extra is meant for the extra output that is generated in source
 # directory (e.g. generated source from lex/yacc) so I'm not using

Overwriting system defined compilers

Not sure if it is intentional or not, but maybe you shouldn't overwrite the CC and CXX variables in the "config-default.mk", so rather than

CC := gcc
CXX := g++

have

CC ?= gcc
CXX ?= g++

At least it took me some time to figure out that the compiler had been overwritten because of the pretty printing.

Bug in the config-default.mk file

The below code from "config-default.mk" file is buggy.

ifeq ($(origin $(CC)),default)
CC := gcc
endif

It should rather be (no $( ) around the CC), like as,

ifeq ($(origin CC),default)
CC := gcc
endif

Can't figure out how to use SUBDIRS_TGTS for more than one level of subdirectories.

Say I have:

  • a/
    • b1/
      • some c files
    • b2/
      • some c files
      • c1/
        • some c files
      • c2/
        • some c files
    • b3/
      • some c files

a/Rules.mk:

SUBDIRS = b1 b2 b3
LIB_A := $(OBJPATH)/liba.o
$(LIB_A)_DEPS = $(SUBDIRS_TGTS)
TARGETS := $(LIB_A)

a/b1/Rules.mk, a/b3/Rules.mk, a/b2/c1/Rules.mk, a/b2/c2/Rules.mk:

SRCS := *.c

a/b2/Rules.mk:

SUBDIRS = c1 c2
SRCS := *.c
TARGETS = b2.o
b2.o_DEPS = $(OBJS_$(d)) $(SUBDIRS_TGTS)

Is there some way to rewrite a/b2/Rules.mk to not need the intermediate target? Maybe something like:

SUBDIRS = c1 c2
SRCS := *.c
TARGETS = $(OBJS_$(d)) $(SUBDIRS_TGTS)

Suggestion: Include targets in default clean rule

When experimenting with nonrec-make, I ran into an issue with adding targets with full paths specified to CLEAN for a subdirectory. I modified the CLEAN_$(d) assignment in footer.mk so they wouldn't have OBJPATH prefixed, but then what I really wanted for our project--with far too many directories and targets--was to not have to assign CLEAN for the typical case, so I added $(TARGETS) to the rule as well:

# Save CLEAN for this directory, also including explicitly specified
# targets. Note that we do not include targets specified without a
# full path as they are output to $(OBJPATH), which is handled by the
# main clean rule.
#
CLEAN_$(d) := $(CLEAN_$(d)) $(filter /%,$(CLEAN) $(TARGETS)) $(addprefix $(d)/,$(filter-out /%,$(CLEAN)))

tgt_rule changes order of dependencies

I had an issue where an executable failed to link. It turned out that because a static library that is a part of the build process was specified with an absolute path, it was appearing first as a dependency due to the order of assignment to abs_deps in tgt_rule -- first absolute, then relative.

Because the static library was listed first, its functions were considered "not used" and the symbols were dropped before gcc got around to looking at the object files containing references to those functions.

As a work-around, I re-ordered to have relative dependencies listed first, then absolute.

I think an ideal solution would be to figure out how to not change the order of the dependencies... but I haven't yet had a chance to look into if that's possible and what would be involved.

SUBDIRS question

First off, thanks for sharing this. It's so easy to use.

I've noticed one bit of odd behavior though. I tried to create a unit test target in a subdir of a directory with a static library target, but the unit test would never get built. Stranger still, the unit test's obj directory is visible in the output when I run make clean_tree.

I created a simpler test that will show what I am talking about. Using the current master (e486147), I modified Rules.top to point to a new ex3 directory with these files:

ex3/hello.cpp
ex3/Rules.mk
ex3/goodbye/goodbye.cpp
ex3/goodbye/Rules.mk

hello.cpp and goodbye.cpp are just simple "Hello world" files. The contents of the two Rules.mk files are:

$ cat ex3/Rules.mk
TARGETS := hello
SUBDIRS := goodbye

hello_DEPS = hello.o
$ cat ex3/goodbye/Rules.mk
TARGETS := goodbye

goodbye_DEPS = goodbye.o

When I run make, I only get this:

$ make
Rules generated...
CXX /home/xulfir/nonrec-make/ex3/hello.cpp
LINK /home/xulfir/nonrec-make/ex3/obj/hello

When I run make clean_tree I see this though.

$ make clean_tree
Rules generated...
rm -f /home/xulfir/nonrec-make/obj/*
rm -f /home/xulfir/nonrec-make/ex3/obj/*
rm -f /home/xulfir/nonrec-make/ex3/goodbye/obj/*

Is there something I'm doing wrong?
Thanks again!

make dist_clean and custom OBJDIRs

Hi Andrzej,
I've noticed that if I specialize OBJDIR by adding another sub directory, the obj directory will no longer be removed when I run make dist_clean. I've got a proposed solution below, if you are interested.

Other than that, this project has been working very nicely for me. Thanks, again for sharing!
Xulfir

diff --git a/mk/footer.mk b/mk/footer.mk
index ec25b8a..f95a1b8 100644
--- a/mk/footer.mk
+++ b/mk/footer.mk
@@ -38,7 +38,7 @@ dist_clean :: $(OBJPATH)/.fake_file
 else
 dist_clean :: $(OBJPATH)/.fake_file clean_extra_$(d)
 endif
-       rm -rf $(<D)
+       rm -rf $(subst $(OBJDIR),$(OBJBASE),$(<D))

 #### Per directory targets ####

diff --git a/mk/skel.mk b/mk/skel.mk
index f7305c2..900533b 100644
--- a/mk/skel.mk
+++ b/mk/skel.mk
@@ -93,8 +93,10 @@ AUTO_TGTS := %.o

 # Where to put the compiled objects.  You can e.g. make it different
 # depending on the target platform (e.g. for cross-compilation a good
-# choice would be OBJDIR := obj/$(HOST_ARCH)) or debugging being on/off.
-OBJDIR := obj
+# choice would be OBJDIR := $(OBJBASE)/$(HOST_ARCH)) or debugging being
+# on/off.
+OBJBASE := obj
+OBJDIR := $(OBJBASE)
 OBJPATH = $(d)/$(OBJDIR)

Extending functionality without touching the source of nonrec-make

I'm not sure whether my assumption is right, but I think the best way to use nonrec-make is to NOT touch it's sources, just extend the functionality via Rules.top/.mk. This way whenever a new version appears you don't need to merge your own changes with the original - just drop them into mk folder and it's done.

So the first question is - am I right? Is this the right and intended "way", or maybe users should change some internal files, like def_rules.mk?

The problem I'm facing now is adding a new compilation command for assembly files in a "generic" way. I'd like to do that by just having this in my Rules.top:

COMPILE.S = $(call echo_cmd,AS $<) $(AS) $(ASLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
AUTO_RULES += .o:.S

But the problem is, that AUTO_RULES are used to define skeleton macro BEFORE Rules.top is evaluated, so in reality I cannot alter it this way - I have to modify the contents of def_rules.mk to do that. If I modify that, then I'll have a problem with each update of the base nonrec-make files...

Any suggestions? Would that be possible to have the skeleton macro defined AFTER Rules.top is parsed, or would that make no sense? Maybe it would be a good idea to add sth like initial.mk (similar to final.mk), to define stuff at the very beginning? A change to AUTO_RULES would be placed there (obviously all overridable / extendable variables would have to use ?= or += in that case instead of :=) and problem would be solved.

Install example

I ended up figuring out that the following code added to the top-level Makefile:

INSTALLABLES = $(filter-out %.o,$(call subtree_tgts,$(d)))

install :
    @echo "example install libs: $(filter %.$(SOEXT),$(INSTALLABLES)) -> $(TOP)/$(HOST_ARCH)/lib"
    @echo "example install exes: $(filter-out %.a %.$(SOEXT),$(INSTALLABLES)) -> $(TOP)/$(HOST_ARCH)/bin"

does what I'd like for installing products. It might be nice to have an example install rule (maybe better worked out, with dependencies in the top bin/lib directory) as well as an example of generated code (i.e. a header generated with a Perl script).

Also I'd suggest that the default be to process the subtree under the current directory, and use "make dir" to make only that subdirectory's targets (principle of least surprise compared to a typical recursive makefile build).

How to shorten OBJPATH when doing out-of-tree builds

When you redefine OBJPATH to be an absolute path to the build folder like this:
OBJPATH = $(TOP)/build/$(d)/$(OBJDIR)
The hierarchy of folders "inside" gets really long, because $(d) is an absolute path. It is possible to make it a bit shorter in case you don't need absolute paths (no "external" dependencies) by just dropping $(TOP) from the beginning of $(d) like this:
OBJPATH = $(TOP)/build/$(subst $(TOP),,$(d))/$(OBJDIR)
But in this approach (out-of-tree build) the OBJDIR part is actually useless. Dropping it from the OBJPATH above or re-defininig it to be empty does not work:

Rules generated ...
make: *** No rule to make target `/c/Users/freddie/Documents/1/stm32_blink_led/b
uild///.fake_file', needed by `/c/Users/freddie/Documents/1/stm32_blink_led/buil
d///main.o'.  Stop.

Would that be possible? I think that out-of-tree build is a more general and universal approach, so it would be a nice improvement. Maybe that is already changed in the new branch - I've seen some changes regarding .fake_file there?

Is it possible to make it even simplier?

Hi!

Nice idea - I managed to get it to work with compiler for bare-metal ARM microcontrollers.

Problem nr 1:
I was wondering if it's possible to make this even simplier... If I'd have such layout:
somefile.c
dir/
dir/somefile_in_dir.c
dir/subdir/
dir/subdir/somefile_in_subdir.c

Then for dir/subdir I have a very trivial Rules.mk - *.c - and that's enough. But in this case - when there is more then 1 level of "depth" I actually need to create some archived library of dir to make it work, otherwise the Rules.top assume that the only dependancy is dir/somefile_in_dir.c, not going any deeper.

Is there any solution of such "problem", so that it would not be necessary to create separate TARGETS for each of such cases?

Problem nr 2:
When you need to create the final executable or some sub-target is there a simple way to give dependancies as a wildcard? For example there are 100 *.c files in the folder, so you just do SRSC := *.c, but you cannot do sometarget_DEPS = *.o, even using $(SRCS) with changed suffix ($(SRCS:%.c=%.o)) does not work (or maybe I was doing it wrong?). In other words - is there some simple way to say that some target depends on all compiled SRCS?

Problem nr 3:
Is there a nice way to name the target using current relative path? sth like lib_sub1_sub2_sub3.a for a target in sub1/sub2/sub3 folder?

Thx in advance! (;

License

Andrzej, can you clarify what license you're releasing nonre-make under? Is using it in a commercial setting okay?
Thanks!

Build vs. target targets

I've extrapolated the terminology in gcc's configure options to mean that when I'm building my software, build is the machine I'm building on and target is the machine the software will run on.

I'm running up against a problem when target is a different architecture than build. Have you come across anything like this, or thought about it?

There are several build tools mixed into my code base. For now, I've defined a set of BUILD_xx variables as well as a BUILD_TARGETS equivalent of TARGETS and OBJPATH_BUILD for OBJPATH (should be BUILD_OBJPATH, I guess).

In footer.mk, just above the CLEAN_$(d) := assignment, I've got:

# Set things up to build targets to run on the build machine if required.
#
ifneq ($(strip $(BUILD_TARGETS)),)
  $(BUILD_TARGETS): CC = $(BUILD_CC)
  $(BUILD_TARGETS): CXX = $(BUILD_CXX)
  $(BUILD_TARGETS): LDFLAGS = $(BUILD_LDFLAGS)
  $(BUILD_TARGETS): ARCH = $(BUILD_ARCH)
endif

This generally works when I have something like:

SRCS := *.c generated.c
SRCS_EXCLUDES := generator.c
daemon := $(d)/daemon
$(daemon)_DEPS = $(OBJS_$(d))

generator := $(OBJPATH_BUILD)/generator
$(generator)_DEPS = generator.o

generated.c := $(OBJPATH)/generated.c
$(generated.c)_DEPS := $(generator)
$(generated.c)_CMD := $(call echo_cmd,Gen $@) $(generator) > $@

TARGETS = $(daemon) $(generated.c)
BUILD_TARGETS = $(generator)

However, when a subdirectory contains only a BUILD_TARGETS entry and no TARGETS, SRCS unsurprisingly does not work well:

CPPFLAGS_$(d) := -DTOOL
SRCS := tool_a.c tool_b.c tool_c.c
SRCS_VPATH := $(TOP)/src/some/library
SRCS += library_bit.c # needs to be built here with -DTOOL
tool := $(d)/tool
$(tool)_DEPS = $(OBJS_$(d))
BUILD_TARGETS = $(tool)

If machine is ARM and build is amd64, tool's OBJS will be built for ARM, but an amd64 linker will be used to try to create tool.

Some thoughts I've had, but not experimented with:

  • Add a $(tool)_SRCS and $(tool)_OBJS_$(d)
  • Add a BUILD_SRCS and BUILD_OBJS_$(d)
  • Simply note if there are no TARGETS and there are BUILD_TARGETS, assume SRCS are meant to be used for BUILD_TARGETS. (Not so nice if this is more like the first case, but instead of building a daemon, we simply want to compile all the .c files in the current directory for use by some target in the parent directory.)

Any other suggestions or different insight into how to solve the problem?

(Edited to correct OBJDIR to OBJPATH.)

Autogenerated / Installed source and dependencies

In the def_rules.mk you have rules for flex/bison, but unfortunately there are no examples of this.

If an existing .c file includes the auto-generated .h file, it can't generate the dependency file because the compilation fails on the missing file. I'm guessing that you dealt with this by manually specifying the dependency... is this true?

In the case I am hitting. each directory is an independent library, and is expected to install its header files to a common area (INC_DIR in this case). Other source files include those headers without having to know the path to the library's source, but the compilation fails because those headers haven't yet been installed to the common area.

It seems like I need two approaches to generate the dependency files; if the files don't exist, run a special tool to generate the dependencies without compiling (I have a shell script that uses gcc -MG and fixes up missing dependencies to assume they are in INC_DIR). If the dependency files exist, go ahead and use them and update them as a side-effect of the compilation.

Am I missing something that should make this just work?

Thank you for your efforts on this tool; I think it'll save me a fair amount of effort overall.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.