mozilla

Revision 371675 of Makefiles - Best practices and suggestions

  • Revision slug: How_Mozilla's_build_system_works/Makefiles_-_Best_practices_and_suggestions
  • Revision title: Makefiles - Best practices and suggestions
  • Revision id: 371675
  • Created:
  • Creator: lahabana
  • Is current revision? Yes
  • Comment Added the best practices tag

Revision Content

  • Laundry list
    • makefiles should remove all content that it will generate.
    • prefer library rules whenever possible or create library rules that are missing.
    • dependency builds - A second invocation of make within a sandbox should always be a NOP.
    • hardcoded values - avoid them like the plague.  Use variable assignments, autogen paths and dynamic file gathering.
    • platform specific - avoid this logic whenever possible.
      • Code as little platform specific logic within a makefile as possible, a conditional and compiler flags at most should be needed.  For ex use defines within sources to shift platform logic into your source code.
      • If a unit test is platform specific write your makefile so it can be used on all platforms but only do work on the specific hardware or class of hardware.
      • For classes of hardware (unix/windows) place your makefile in a subdirectory, unix/Makefile.in
  • Always include dependencies when creating a target
    • Initial make call should always be the workhorse: build, generate, deploy, install, etc.
    • All subsequent make calls must become a NOP unless sources or dependencies change or have been removed.
    • Incorrect dependencies will contribute to wasted cycles and can contribute to circular dependencies.
  • Directory dependencies
    • Do not use directories as a dependency for generated targets, ever.  Any activity within a directory will alter inodes forcing targets to always be stale.
      • For ex, individual unit tests would invalidate all prior test activity whenever a test touched a timestamp file in the directory to signal success.
    • Parallel make: add an explicit timestamp dependency (.done) that make can synchronize threaded calls on to avoid a race condition.
# Transient directory for storing timestamps
TS=.ts

#####################################################
## Extra dep needed to synchronize parallel execution
#####################################################
$(TS): $(TS)/.done
$(TS)/.done:
  $(MKDIR) -p $(dir $@)
  touch $@ 

#  "clean" target
GARBAGE_DIRS += $(TS)
  • Maintain clean targets - makefiles should be able to remove all content that is generated so "make clean" will return the sandbox/directory back to a clean state.
targets = foo bar tans
timestampDIR = .ts

all: $(timestampDIR) $(targets)

%: %.c
    $(CC) -o $@ $<

$(timestampDIR):
    $(MKDIR) $@

# "clean" target
GARBAGE += $(targets)
GARBAGE_DIRS += $(timestampDIR)
  • Wrapper check/unit tests with a ENABLE_TESTS conditional so tests can be disabled on demand.
ifdef ENABLE_TESTS 
    ifeq ($(NULL),$(filter WINNT OS2,$(OS_ARCH)))
        DIRS += test 
    endif # WIN
endif # ENABLE_TESTS

Revision Source

<ul>
  <li>Laundry list
    <ul>
      <li>makefiles should remove all content that it will generate.</li>
      <li>prefer library rules whenever possible or create library rules that are missing.</li>
      <li>dependency builds - A second invocation of make within a sandbox should always be a NOP.</li>
      <li>hardcoded values - avoid them like the plague.&nbsp; Use variable assignments, autogen paths and dynamic file gathering.</li>
      <li>platform specific - avoid this logic whenever possible.
        <ul>
          <li>Code as little platform specific logic within a makefile as possible, a conditional and compiler flags at most should be needed.&nbsp; For ex use defines within sources to shift platform logic into your source code.</li>
          <li>If a unit test is platform specific write your makefile so it can be used on all platforms but only do work on the specific hardware or class of hardware.</li>
          <li>For classes of hardware (unix/windows)&nbsp;place your makefile in a subdirectory, unix/Makefile.in</li>
        </ul>
      </li>
    </ul>
  </li>
  <li>Always include dependencies when creating a target
    <ul>
      <li>Initial make call should always be the workhorse:&nbsp;build, generate, deploy, install, etc.</li>
      <li>All subsequent make calls must become a NOP unless sources or dependencies change or have been removed.</li>
      <li>Incorrect dependencies will contribute to wasted cycles and can contribute to circular dependencies.</li>
    </ul>
  </li>
  <li>Directory dependencies
    <ul>
      <li>Do not use directories as a dependency for generated targets, ever.&nbsp; Any activity within a directory will alter inodes forcing targets to always be stale.
        <ul>
          <li>For ex, individual unit tests would invalidate all prior test activity whenever a test touched a timestamp file in the directory to signal success.</li>
        </ul>
      </li>
      <li>Parallel make: add an explicit timestamp dependency (.done) that make can synchronize threaded calls on to avoid a race condition.</li>
    </ul>
  </li>
</ul>
<pre>
<span style="color:#008800;">#&nbsp;Transient directory for storing timestamps
TS=.ts

#####################################################
</span><span style="color:#008800;">## Extra dep needed to synchronize parallel execution
</span><span style="color:#008800;">#####################################################
</span><span style="color:#008800;">$(TS): $(TS)/.done
</span><span style="color:#008800;">$(TS)/.done:
&nbsp;&nbsp;</span><span style="color:#008800;">$(MKDIR) -p $(dir $@)
&nbsp;&nbsp;</span><span style="color:#008800;">touch $@ 

# &nbsp;"clean" target
GARBAGE_DIRS&nbsp;+= $(TS)</span>
</pre>
<ul>
  <li>Maintain clean targets - makefiles should be able to remove all content that is generated so "make clean" will return the sandbox/directory back to a clean state.</li>
</ul>
<pre>
<span style="color: rgb(51, 153, 51);">targets = foo bar tans
timestampDIR = .ts

all: $(timestampDIR) $(targets)

%: %.c
    $(CC) -o $@ $&lt;

$(timestampDIR):
    $(MKDIR) $@

# "clean" target
GARBAGE += $(targets)
GARBAGE_DIRS += $(timestampDIR)</span>
</pre>
<ul>
  <li>Wrapper check/unit tests with a ENABLE_TESTS conditional so tests can be disabled on demand.</li>
</ul>
<pre class="bz_comment_text" id="comment_text_6">
<span style="color: rgb(51, 153, 51);"><span class="quote">ifdef ENABLE_TESTS 
    ifeq ($(NULL),$(filter WINNT OS2,$(OS_ARCH)))
        DIRS += test 
    endif # WIN
endif # ENABLE_TESTS</span></span>
</pre>
Revert to this revision