aboutsummaryrefslogtreecommitdiff
path: root/common/makefiles/MakeHelpers.gmk
blob: aa495323b5d9876c9f76b11b7072c47396d7a18f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
#
# Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# This code is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 2 only, as
# published by the Free Software Foundation.  Oracle designates this
# particular file as subject to the "Classpath" exception as provided
# by Oracle in the LICENSE file that accompanied this code.
#
# This code is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
# version 2 for more details (a copy is included in the LICENSE file that
# accompanied this code).
#
# You should have received a copy of the GNU General Public License version
# 2 along with this work; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
# or visit www.oracle.com if you need additional information or have any
# questions.
#

################################################################
#
# This file contains helper functions for the top-level Makefile that does
# not depend on the spec.gmk file having been read. (The purpose of this 
# file is ju to avoid cluttering the top-level Makefile.)
#
################################################################

ifndef _MAKEHELPERS_GMK
_MAKEHELPERS_GMK := 1

##############################
# Stuff to run at include time
##############################

# Find out which variables were passed explicitely on the make command line. These
# will be passed on to sub-makes, overriding spec.gmk settings.
MAKE_ARGS=$(foreach var,$(subst =command,,$(filter %=command,$(foreach var,$(.VARIABLES),$(var)=$(firstword $(origin $(var)))))),$(var)=$($(var)))

list_alt_overrides_with_origins=$(filter ALT_%=environment ALT_%=command,$(foreach var,$(.VARIABLES),$(var)=$(firstword $(origin $(var)))))
list_alt_overrides=$(subst =command,,$(subst =environment,,$(list_alt_overrides_with_origins)))

# Store the build times in this directory.
BUILDTIMESDIR=$(OUTPUT_ROOT)/tmp/buildtimes

# Global targets are possible to run either with or without a SPEC. The prototypical
# global target is "help". 
global_targets=help jprt% bridgeBuild

##############################
# Functions
##############################

define CheckEnvironment
    # Find all environment or command line variables that begin with ALT.
    $(if $(list_alt_overrides),
        @$(PRINTF) "\nWARNING: You have the following ALT_ variables set:\n"
    @$(PRINTF) "$(foreach var,$(list_alt_overrides),$(var)=$$$(var))\n"
    @$(PRINTF) "ALT_ variables are deprecated and will be ignored. Please clean your environment.\n\n"
    )
endef

### Functions for timers

# Record starting time for build of a sub repository.
define RecordStartTime
    $(MKDIR) -p $(BUILDTIMESDIR)
    $(DATE) '+%Y %m %d %H %M %S' | $(NAWK) '{ print $$1,$$2,$$3,$$4,$$5,$$6,($$4*3600+$$5*60+$$6) }' > $(BUILDTIMESDIR)/build_time_start_$1
    $(DATE) '+%Y-%m-%d %H:%M:%S' > $(BUILDTIMESDIR)/build_time_start_$1_human_readable
endef

# Record ending time and calculate the difference and store it in a
# easy to read format. Handles builds that cross midnight. Expects
# that a build will never take 24 hours or more. 
define RecordEndTime
    $(DATE) '+%Y %m %d %H %M %S' | $(NAWK) '{ print $$1,$$2,$$3,$$4,$$5,$$6,($$4*3600+$$5*60+$$6) }' > $(BUILDTIMESDIR)/build_time_end_$1
    $(DATE) '+%Y-%m-%d %H:%M:%S' > $(BUILDTIMESDIR)/build_time_end_$1_human_readable
    $(ECHO) `$(CAT) $(BUILDTIMESDIR)/build_time_start_$1` `$(CAT) $(BUILDTIMESDIR)/build_time_end_$1` $1 | \
        $(NAWK) '{ F=$$7; T=$$14; if (F > T) { T+=3600*24 }; D=T-F; H=int(D/3600); \
        M=int((D-H*3600)/60); S=D-H*3600-M*60; printf("%02d:%02d:%02d %s\n",H,M,S,$$15); }' \
        > $(BUILDTIMESDIR)/build_time_diff_$1
endef

# Find all build_time_* files and print their contents in a list sorted
# on the name of the sub repository.
define ReportBuildTimes
    $(BUILD_LOG_WRAPPER) $(PRINTF) -- "----- Build times -------\nStart %s\nEnd   %s\n%s\n%s\n-------------------------\n" \
        "`$(CAT) $(BUILDTIMESDIR)/build_time_start_TOTAL_human_readable`" \
        "`$(CAT) $(BUILDTIMESDIR)/build_time_end_TOTAL_human_readable`" \
        "`$(LS) $(BUILDTIMESDIR)/build_time_diff_* | $(GREP) -v _TOTAL | $(XARGS) $(CAT) | $(SORT) -k 2`" \
        "`$(CAT) $(BUILDTIMESDIR)/build_time_diff_TOTAL`"
endef

define ResetAllTimers
    $$(shell $(MKDIR) -p $(BUILDTIMESDIR) &&  $(RM) $(BUILDTIMESDIR)/build_time_*)
endef

define StartGlobalTimer
    $(call RecordStartTime,TOTAL)
endef

define StopGlobalTimer
    $(call RecordEndTime,TOTAL)
endef

### Functions for managing makefile structure (start/end of makefile and individual targets)

# Do not indent this function, this will add whitespace at the start which the caller won't handle
define GetRealTarget
$(strip $(if $(MAKECMDGOALS),$(MAKECMDGOALS),default))
endef

# Do not indent this function, this will add whitespace at the start which the caller won't handle
define LastGoal
$(strip $(lastword $(call GetRealTarget)))
endef

# Check if the current target is the final target, as specified by
# the user on the command line. If so, call AtRootMakeEnd.
define CheckIfMakeAtEnd
    # Check if the current target is the last goal
    $(if $(filter $@,$(call LastGoal)),$(call AtMakeEnd))
    # If the target is 'foo-only', check if our goal was stated as 'foo'
    $(if $(filter $@,$(call LastGoal)-only),$(call AtMakeEnd))
endef

# Hook to be called when starting to execute a top-level target
define TargetEnter
    $(BUILD_LOG_WRAPPER) $(PRINTF) "## Starting $(patsubst %-only,%,$@)\n"
    $(call RecordStartTime,$(patsubst %-only,%,$@))
endef

# Hook to be called when finish executing a top-level target
define TargetExit
    $(call RecordEndTime,$(patsubst %-only,%,$@))
    $(BUILD_LOG_WRAPPER) $(PRINTF) "## Finished $(patsubst %-only,%,$@) (build time %s)\n\n" \
        "`$(CAT) $(BUILDTIMESDIR)/build_time_diff_$(patsubst %-only,%,$@) | $(CUT) -f 1 -d ' '`"
    $(call CheckIfMakeAtEnd)
endef

# Hook to be called as the very first thing when running a normal build
define AtMakeStart
    $(if $(findstring --jobserver,$(MAKEFLAGS)),$(error make -j is not supported, use make JOBS=n))
    $(call CheckEnvironment)
    @$(PRINTF) $(LOG_INFO) "Running make as '$(MAKE) $(MFLAGS) $(MAKE_ARGS)'\n"
    @$(PRINTF) "Building $(PRODUCT_NAME) for target '$(call GetRealTarget)' in configuration '$(CONF_NAME)'\n\n"
    $(call StartGlobalTimer)
endef

# Hook to be called as the very last thing for targets that are "top level" targets
define AtMakeEnd
    [ -f $(SJAVAC_SERVER_DIR)/server.port ] && echo Stopping sjavac server && $(TOUCH) $(SJAVAC_SERVER_DIR)/server.port.stop; true
    $(call StopGlobalTimer)
    $(call ReportBuildTimes)
    @$(PRINTF) "Finished building $(PRODUCT_NAME) for target '$(call GetRealTarget)'\n"
    $(call CheckEnvironment)
endef

### Functions for parsing and setting up make options from command-line

define FatalError
    # If the user specificed a "global" target (e.g. 'help'), do not exit but continue running
    $$(if $$(filter-out $(global_targets),$$(call GetRealTarget)),$$(error Cannot continue))
endef

define ParseLogLevel
    ifeq ($$(origin VERBOSE),undefined)
        # Setup logging according to LOG (but only if VERBOSE is not given)

        # If the "nofile" argument is given, act on it and strip it away
        ifneq ($$(findstring nofile,$$(LOG)),)
          # Reset the build log wrapper, regardless of other values
          override BUILD_LOG_WRAPPER=
          # COMMA is defined in spec.gmk, but that is not included yet
          COMMA=,
          # First try to remove ",nofile" if it exists
          LOG_STRIPPED1=$$(subst $$(COMMA)nofile,,$$(LOG))
          # Otherwise just remove "nofile"
          LOG_STRIPPED2=$$(subst nofile,,$$(LOG_STRIPPED1))
          # We might have ended up with a leading comma. Remove it
          LOG_STRIPPED3=$$(strip $$(patsubst $$(COMMA)%,%,$$(LOG_STRIPPED2)))
          LOG_LEVEL:=$$(LOG_STRIPPED3)
        else
          LOG_LEVEL:=$$(LOG)
        endif

        ifeq ($$(LOG_LEVEL),)
            # Set LOG to "warn" as default if not set (and no VERBOSE given)
            override LOG_LEVEL=warn
        endif
        ifeq ($$(LOG_LEVEL),warn)
            VERBOSE=-s
        else ifeq ($$(LOG_LEVEL),info)
            VERBOSE=-s
        else ifeq ($$(LOG_LEVEL),debug)
            VERBOSE=
        else ifeq ($$(LOG_LEVEL),trace)
            VERBOSE=
        else
            $$(info Error: LOG must be one of: warn, info, debug or trace.)
            $$(eval $$(call FatalError))
        endif
    else
        # Provide resonable interpretations of LOG_LEVEL if VERBOSE is given.
        ifeq ($(VERBOSE),)
            LOG_LEVEL:=debug
        else
            LOG_LEVEL:=warn
        endif
        ifneq ($$(LOG),)
            # We have both a VERBOSE and a LOG argument. This is OK only if this is a repeated call by ourselves,
            # but complain if this is the top-level make call.
            ifeq ($$(MAKELEVEL),0)
                $$(info Cannot use LOG=$$(LOG) and VERBOSE=$$(VERBOSE) at the same time. Choose one.)
                $$(eval $$(call FatalError))
            endif
        endif
    endif
endef

define ParseConfAndSpec
    ifneq ($$(filter-out $(global_targets),$$(call GetRealTarget)),)
        # If we only have global targets, no need to bother with SPEC or CONF
        ifneq ($$(origin SPEC),undefined)
            # We have been given a SPEC, check that it works out properly
            ifeq ($$(wildcard $$(SPEC)),)
                $$(info Cannot locate spec.gmk, given by SPEC=$$(SPEC))
                $$(eval $$(call FatalError))
            endif
            ifneq ($$(origin CONF),undefined)
                # We also have a CONF argument. This is OK only if this is a repeated call by ourselves,
                # but complain if this is the top-level make call.
                ifeq ($$(MAKELEVEL),0)
                    $$(info Cannot use CONF=$$(CONF) and SPEC=$$(SPEC) at the same time. Choose one.)
                    $$(eval $$(call FatalError))
                endif
            endif
            # ... OK, we're satisfied, we'll use this SPEC later on
        else
            # Find all spec.gmk files in the build output directory
            output_dir=$$(root_dir)/build
            all_spec_files=$$(wildcard $$(output_dir)/*/spec.gmk)
            ifeq ($$(all_spec_files),)
                $$(info No configurations found for $$(root_dir)! Please run configure to create a configuration.)
                $$(eval $$(call FatalError))
            endif
            # Extract the configuration names from the path
            all_confs=$$(patsubst %/spec.gmk,%,$$(patsubst $$(output_dir)/%,%,$$(all_spec_files)))

            ifneq ($$(origin CONF),undefined)
                # User have given a CONF= argument.
                ifeq ($$(CONF),)
                    # If given CONF=, match all configurations
                    matching_confs=$$(strip $$(all_confs))
                else
                    # Otherwise select those that contain the given CONF string
                    matching_confs=$$(strip $$(foreach var,$$(all_confs),$$(if $$(findstring $$(CONF),$$(var)),$$(var))))
                endif
                ifeq ($$(matching_confs),)
                    $$(info No configurations found matching CONF=$$(CONF))
                    $$(info Available configurations:)
                    $$(foreach var,$$(all_confs),$$(info * $$(var)))
                    $$(eval $$(call FatalError))
                else
                    ifeq ($$(words $$(matching_confs)),1)
                        $$(info Building '$$(matching_confs)' (matching CONF=$$(CONF)))
                    else
                        $$(info Building target '$(call GetRealTarget)' in the following configurations (matching CONF=$$(CONF)):)
                        $$(foreach var,$$(matching_confs),$$(info * $$(var)))
                    endif
                endif

                # Create a SPEC definition. This will contain the path to one or more spec.gmk files.
                SPEC=$$(addsuffix /spec.gmk,$$(addprefix $$(output_dir)/,$$(matching_confs)))
            else
                # No CONF or SPEC given, check the available configurations
                ifneq ($$(words $$(all_spec_files)),1)
                    $$(info No CONF given, but more than one configuration found in $$(output_dir).)
                    $$(info Available configurations:)
                    $$(foreach var,$$(all_confs),$$(info * $$(var)))
                    $$(info Please retry building with CONF=<config pattern> (or SPEC=<specfile>))
                    $$(eval $$(call FatalError))
                endif

                # We found exactly one configuration, use it
                SPEC=$$(strip $$(all_spec_files))
            endif
        endif
    endif
endef

### Convenience functions from Main.gmk

# Cleans the component given as $1
define CleanComponent
    @$(PRINTF) "Cleaning $1 build artifacts ..."
    @($(CD) $(OUTPUT_ROOT) && $(RM) -r $1)
    @$(PRINTF) " done\n"
endef

endif # _MAKEHELPERS_GMK