So now that I know what I want my
Makefile
to actually do,
it's time to actually write one for the current project, as well as create a
template Makefile
for use with future projects (though I
expect it will be a while until I have need of it elsewhere).
Common Header
Variables
There were four common variables across the two build-targets that I noted in
my previous post:
build_directory
:- The temporary build-directory (e.g.,
/tmp/build-process
); current_builds
:- The local user-directory where final build-results will be dropped (e.g.,
~/Current_Builds
); project_copy
:- The copy of the original
project_directory
(below), created inside thebuild_directory
, where deployable files will be gathered in order to be archived into the final package-tarball (e.g.,/tmp/build-process/{project_name}
); project_directory
:- The original project-directory (e.g.,
~/IDreamInCode/{project_name}
); project_name
:- The name of the project (e.g.,
idic
);
Makefile
. I'm also going to add
a project-root variable that'll eventually be used for dependent-project
build-processing:
#############################
# PROJECT SETTINGS #
#############################
# The root directory for all projects that use this build-
# process -- MUST be the same if project-dependencies are
# going to be preserved!
PROJECT_ROOT=~/IDreamInCode
# The final destination directory for all project-build
# results. This is local to the user!
CURRENT_BUILDS=~/Current_Builds
# The name of the project being built. It'd be nice to
# have this auto-detected in some way, but for now, I'm
# OK with it being a "magic" string value
PROJECT_NAME=idic
# The directory that will be created to house all of the
# temporary files for the build-process
BUILD_DIRECTORY=/tmp/$(PROJECT_NAME)-build
# The directory that will be created to house all of the
# temporary files for the build-process
PROJECT_COPY=$(BUILD_DIRECTORY)/$(PROJECT_NAME)
# The root directory of the PROJECT - assumes that all
# projects reside in the $(PROJECT_ROOT) directory,
# in a directory named after the project.
PROJECT_DIRECTORY=$(PROJECT_ROOT)/$(PROJECT_NAME)
#############################
# BUILD SETTINGS #
#############################
#############################
# MAIN BUILD TARGETS #
#############################
#############################
# COMMON BUILD-TASK TARGETS #
#############################
The values of these project-settings variables, defined as they are in the head
of the Makefile
and outside (before) any of the targets allows the
to be set and available inside the targets. For example, adding this
target to the current Makefile
:
show_vars:
@echo "- show_vars ----------------------------"
@echo "BUILD_DIRECTORY ..... $(BUILD_DIRECTORY)"
@echo "CURRENT_BUILDS ...... $(CURRENT_BUILDS)"
@echo "PROJECT_COPY ........ $(PROJECT_COPY)"
@echo "PROJECT_DIRECTORY ... $(PROJECT_DIRECTORY)"
@echo "PROJECT_NAME ........ $(PROJECT_NAME)"
@echo "PROJECT_ROOT ........ $(PROJECT_ROOT)"
@echo "- /show_vars ---------------------------"
and running make
(with the new target as the default target), yields:
- show_vars ---------------------------- BUILD_DIRECTORY ..... /tmp/idic-build CURRENT_BUILDS ...... ~/Current_Builds PROJECT_COPY ........ /tmp/idic-build/idic PROJECT_DIRECTORY ... ~/IDreamInCode/idic PROJECT_NAME ........ idic PROJECT_ROOT ........ ~/IDreamInCode - /show_vars ---------------------------
I'll change and re-use theThose same variables can also be used as parts or all of target-level variables. By way of example, consider the starting-point for theshow_vars
target as I work through the other examples here... if output shown here has
- show_vars ----------------------------in it, it originated withshow_vars
.
local_all
target:
local_all: ENV_PREFIX=LOCAL
local_all: BUILD_PATHS=$(BUILD_PATHS_ALL)
local_all: BUILD_OUTPUT=$(CURRENT_BUILDS)/$(ENV_PREFIX).$(PROJECT_NAME)*
local_all:
# TODO: Implement recursive build for projects that
# this project uses:
# cd $(PROJECT_ROOT)/[project];$(MAKE) local_all
# TODO: Determine if there is anything else that needs
# to be done for a $(ENV_PREFIX) build here.
# Build complete for $(ENV_PREFIX) version of $(PROJECT_NAME) project:
# + Source ... $(PROJECT_DIRECTORY)/[$(BUILD_PATHS)]
# + Output ... $(BUILD_OUTPUT)
If make local_all
is executed, it yields:
# TODO: Implement recursive build for projects that # this project uses: # cd ~/IDreamInCode/[project];make local_all # TODO: Determine if there is anything else that needs # to be done for a LOCAL build here. # Build complete for LOCAL version of idic project: # + Source ... ~/IDreamInCode/idic/[etc usr var] # + Output ... ~/Current_Builds/LOCAL.idic*This also shows that the target-level variables can be used to build other variables-values for the target — note the results of
$(BUILD_OUTPUT)
in the output, whose value originated from the global $(CURRENT_BUILDS)
and $(PROJECT_NAME)
and the target-level $(ENV_PREFIX)
values... The target-level variables also carry through to any targets
called afer they are defined. That is, if the $(ENV_PREFIX)
), it's set to LOCAL
inside - show_vars ---------------------------- BUILD_DATETIME ...... Mon Jan 9 14:18:48 MST 2017 BUILDER_NAME ........ ballbee BUILD_PATHS_ALL ..... etc usr var BUILD_PATHS_APP ..... usr var/cache BUILD_PATHS_SITE .... etc/apache2/sites-available var/www BUILD_PATHS_SNAP .... etc test_idic usr var ENV_PREFIX .......... LOCAL - /show_vars ---------------------------
Stubbing Out the COMMON BUILD-TASK TARGETS
At this point, my expectation is that theThe first step, then, is generating targets that at least display that they've been called. That doesn't require anything fancy, just the collection of targets and a comment in each of them to the effect of what target they are:
#############################
# COMMON BUILD-TASK TARGETS #
#############################
add_snapshot_files:
# Calling add_snapshot_files
base_all:
# Calling base_all
base_app:
# Calling base_app
base_site:
# Calling base_site
build_directory:
# Calling build_directory
clean_build_directory:
# Calling clean_build_directory
clean_executables:
# Calling clean_executables
copy_to_current_builds:
# Calling copy_to_current_builds
create_documentation:
# Calling create_documentation
create_install_scripts:
# Calling create_install_scripts
create_snapshot_zip:
# Calling create_snapshot_zip
create_tarball:
# Calling create_tarball
current_builds:
# Calling current_builds
fix_environment:
# Calling fix_environment
test:
# Calling test
With that in place, calling all of the relevant targets for each of the two main
targets (#############################
# MAIN BUILD TARGETS #
#############################
snapshot: ZIP_FILE_NAME=$(PROJECT_NAME).zip
snapshot: BUILD_OUTPUT=$(CURRENT_BUILDS)/$(ZIP_FILE_NAME)
snapshot: BUILD_PATHS=$(BUILD_PATHS_SNAP)
snapshot: BUILD_ZIP=$(BUILD_DIRECTORY)/$(ZIP_FILE_NAME)
snapshot: current_builds build_directory base_all add_snapshot_files clean_build_directory create_snapshot_zip
# Build complete for SNAPSHOT of $(PROJECT_NAME) project:
# + Source ... $(PROJECT_DIRECTORY)/[$(BUILD_PATHS)]
# + Output ... $(BUILD_OUTPUT)
local_all: ENV_PREFIX=LOCAL
local_all: BUILD_PATHS=$(BUILD_PATHS_ALL)
local_all: BUILD_OUTPUT=$(CURRENT_BUILDS)/$(ENV_PREFIX).$(PROJECT_NAME)*
local_all: current_builds build_directory base_all clean_build_directory clean_executables fix_environment create_tarball create_install_scripts copy_to_current_builds
# TODO: Implement recursive build for projects that
# this project uses: cd $(PROJECT_ROOT)/[project];$(MAKE) local_all
# TODO: Determine if there is anything else that needs
# to be done for a $(ENV_PREFIX) build here.
# Build complete for $(ENV_PREFIX) version of $(PROJECT_NAME) project:
# + Source ... $(PROJECT_DIRECTORY)/[$(BUILD_PATHS)]
# + Output ... $(BUILD_OUTPUT)
Calling make snapshot
and make local_all
then return,
respectively:
# Calling current_builds # Calling build_directory # Calling base_all # Calling add_snapshot_files # Calling clean_build_directory # Calling create_snapshot_zip # Build complete for SNAPSHOT of idic project: # + Source ... ~/IDreamInCode/idic/[etc test_idic usr var] # + Output ... ~/IDIC_Builds/idic.zip
# Calling current_builds # Calling build_directory # Calling base_all # Calling clean_build_directory # Calling clean_executables # Calling fix_environment # Calling create_tarball # Calling create_install_scripts # Calling copy_to_current_builds # TODO: Implement recursive build for projects that # this project uses: cd ~/IDreamInCode/[project];make local_all # TODO: Determine if there is anything else that needs # to be done for a LOCAL build here. # Build complete for LOCAL version of idic project: # + Source ... ~/IDreamInCode/idic/[etc usr var] # + Output ... ~/IDIC_Builds/LOCAL.idic*which at least demonstrate that the required sub-targets are firing as expected.
Putting some Meat in the Sub-Targets
So now it's time to actually implement the sub-targets. Since I'm not currently concerned about the items inMakefile
for a make snapshot
build that I'll discuss in this post:
current_builds
;build_directory
;base_all
;add_snapshot_files
;clean_build_directory
;create_snapshot_zip
; and- any changes or additions to the main
snapshot
target that arise as a result of any of the earlier targets in execution order. target_name
Makefile
, but I'm not
going to show or discuss them in any great detail. However far
I do get with them, they'll be in the template Makefile
, and
that'll be downloadable at the end of this post.
The current_builds
Target
Ultimately, the current_builds
target is really
only responsible for creating the final destination-directory that all of the
top-levelbuild-processes will drop their final output in. It should hopefully be no great surprise, then, that it's not a terribly complicated target. It does need the
CURRENT_BUILDS
variable set, but that
happens at the top of the Makefile
, so that's already accounted for.
The complete target is:
current_builds:
# Creating local current-builds directory
# at $(CURRENT_BUILDS)
@mkdir -p $(CURRENT_BUILDS)
One item that's worth mentioning here because it'll show up elsewhere later:
One of the things that I find I don't like about make
is
that all of the commands that get executed in a target (like the mkdir
-p $(CURRENT_BUILDS)
here) get echoed out to the console during a
make
run by default. I'm guessing that in a more normalbuild-process, say compiling and assembling a C or C++ program, there may be some advantage to seeing the commands that were run as well as any output from them. I don't expect that most of what I'm going to be doing in any of these targets really needs that degree of visibility, though, so I almost always hide the commands being executed (and their output) by default. That's what the
@
in the @mkdir -p $(CURRENT_BUILDS)
line does. I usually figure
that if I need (or want) output, it's easy enough to generate
it where or if I determine I do want it.
The build_directory
Target
build_directory
is just as simple a target as
current_builds
, because it does much the same thing,
just with a different variable (BUILD_DIRECTORY
, also defined in the
head of the Makefile
) controlling the path of the build-directory to
be created:
build_directory:
# Creating build-directory
# at $(BUILD_DIRECTORY)
@mkdir -p $(BUILD_DIRECTORY)
The base_all
Target
This is where things start to get interesting, I think...One of the main tasks that this build-process has to do is to assemble the project's code into a safe location for further processing. In order to do that, it has to know several things about the project, all of which are based around the project-structure:
- Where the project itself lives (the
PROJECT_DIRECTORY
variable); - Where the safe code-copy is to be made (the
PROJECT_COPY
variable); and - What directories in the project need to be copied (the
BUILD_PATHS
variable)
PROJECT_DIRECTORY
and PROJECT_COPY
are set in the head
of the Makefile
, but BUILD_PATHS
is created by the various
top-level targets:
#############################
# MAIN BUILD TARGETS #
#############################
# ...
snapshot: BUILD_PATHS=$(BUILD_PATHS_SNAP)
# ...
local_all: BUILD_PATHS=$(BUILD_PATHS_ALL)
# ...
They, in turn, refer to other variables (BUILD_PATHS_SNAP
and
BUILD_PATHS_ALL
) that are also defined in the
Makefile
's head.
base_all
also marks the first time that I'm writing
a build-target that uses programs available in the external shell. I ran into some
oddities while I was working this target out, probably because of either something
new in my version of make
(unlikely), or some misunderstanding on my
part about what a target has access to in a Makefile
. Discussion of
that will make more sense after I show the target, though, so here it is:
base_all:
# Creating project-copy directory:
# at $(PROJECT_COPY)
@mkdir -p $(PROJECT_COPY)
# Copying $(BUILD_PATHS)
# to $(PROJECT_COPY):
@for PATH in $(BUILD_PATHS); do \
/bin/cp -r $(PROJECT_DIRECTORY)/$$PATH \
$(PROJECT_COPY)/$$PATH; \
echo "# + $(PROJECT_NAME)/$$PATH copied to $(PROJECT_COPY)/$$PATH"; \
done
Like the previous two targets, base_all
creates
a directory — this one being the place to copy all of the project-code,
defined by the PROJECT_COPY
variable, which is defined in the head
of the Makefile
. That's nothing too new, since it's been done before.
The next step is where things got a bit odd. The target then uses a shell-loop to iterate over the
BUILD_PATHS
(also defined in the head), and makes
a copy of the directory, with all of its children, in the PROJECT_COPY
directory, before displaying messaging indicating that the copy succeeded. The odd
thing, I thought, was that the cp
shell-utility couldn't be found
within the target while it was running. That is, with
base_all:
# ...
@for PATH in $(BUILD_PATHS); do \
cp -r $(PROJECT_DIRECTORY)/$$PATH \
$(PROJECT_COPY)/$$PATH; \
echo "# + $(PROJECT_NAME)/$$PATH copied to $(PROJECT_COPY)/$$PATH"; \
done
in the target-code, execution died in the middle of that target, reporting
/bin/sh: 2: cp: not foundI have no idea why. I've used similar command-structures (though usually calling
rsync
instead of cp
) before and never run into this
issue. Nor, frankly, does there appear to be any mention of this sort of bugaboo
anywhere that I could find with a Google search. To be fair, I may not have hit
on the rightsearch-terms in my efforts, but even so — no mention of this at all leads me to think that there's something odd going on.
Still, fortunately, I could access the
cp
command by providing
the full shell-path to it (found with which cp
in a terminal). I'd
like to figure out why a normal
cp
call is barfing,
especially since the echo
and mkdir
commands
weren't having issues, but the workaround that's in place now will do for me, even
if it does make some assumptions about the system that builds are being generated on.
With this target operational, the build-output is starting to look close to complete:
# Creating local current-builds directory at ~/IDIC_Builds # Creating build-directory at /tmp/idic-build # Creating project-copy directory: # at /tmp/idic-build/idic # Copying etc test_idic usr var # to /tmp/idic-build/idic: # + idic/etc copied to /tmp/idic-build/idic/etc # + idic/test_idic copied to /tmp/idic-build/idic/test_idic # + idic/usr copied to /tmp/idic-build/idic/usr # + idic/var copied to /tmp/idic-build/idic/var # Adding snapshot files and directories to /tmp/idic-build/idicand the build-copy directory is starting to show the source-files: So far, so good...
The add_snapshot_files
Target
As things stand right now, only one file of three (or more) that I expect to
be part of any given project actually exist in my current
project-structure template: the
runTests.py
file that will be called by the test
target. Since the purpose of a snapshot
build is to
copy the entire active codebase for dissemination here on the blog, it needs to
include all the files that might be considered formalmembers of the project, which will, eventually, include the
Makefile
that I'm working
on now, any installation-scripts for the project, and maybe a text-file report of
the output of the last unit-test run. Not all of these files will exist at any given
time, though at least two can be specified right now:
#############################
# PROJECT SETTINGS #
#############################
# ..
# The list of all additional files that need to be added
# to a snapshot build's file-set
SNAPSHOT_PATHS=runTests.py $(PROJECT_NAME)-test-results.txt
# TODO: Once the other files that belong to all projects
# have been defined, add them to the list here,
# including:
# - Makefile
# - install-scripts
# - Others?
Having the SNAPSHOT_PATHS
defined allows the
add_snapshot_files
target to be fully defined:
add_snapshot_files:
# Adding snapshot files and directories to $(PROJECT_COPY)
@for PATH in $(SNAPSHOT_PATHS); do \
if [ -f "$(PROJECT_DIRECTORY)/$$PATH" ]; then \
/bin/cp -r $(PROJECT_DIRECTORY)/$$PATH $(PROJECT_COPY); \
echo "# + $(PROJECT_NAME)/$$PATH copied to $(PROJECT_COPY)"; \
else \
echo "# + $(PROJECT_NAME)/$$PATH skipped"; \
fi; \
done
The output of the add_snapshot_files
target
specifically lists each file in the SNAPSHOT_PATHS
list, so that it
can report on the finsl disposition of each one individually:
# Adding snapshot files and directories to /tmp/idic-build/idic # + idic/runTests.py copied to /tmp/idic-build/idic # + idic/idic-test-results.txt skipped
The clean_build_directory
Target
Deploying or distributing compiled .pyc
files (and presumably
.pyo
files as well) can be troublesome. If the original .py
file that a .pyc
file was compiled from hasn't been modified
since the last time the .pyc
was compiled, it may well contain
references to paths on the original system that it was compiled on. While I've
never seen that cause any actual errors, I have seen cases where
an unrelated error in the code raises an exception, and the traceback that accompanies
it displays those original-system paths. That may not be a big deal. It wasn't
anything more than an annoyance to me the few times I encountered it, but then I
knew the codebase that the error resided in pretty intimately, and had no great
difficulty translating from the liveerror module-paths to the
storedpaths that originated on my local development machine. Someone less familiar with a codebase than I was might well get more than a little frustrated trying to track down the source of a bug when they could be spending the time fixing it. Since I'm writing Python code, and there's no need for me to distribute
.pyc
or .pyo
files, there's no point in keeping them in
the file-set for any build at present. That is what the
clean_build_directory
target is all about:
clean_build_directory:
# Removing files that shouldn't be part of a build
# - Removing all files matching: $(REMOVE_FILE_TYPES)
@for FILE_SPEC in $(REMOVE_FILE_TYPES); do \
for FILE_NAME in `find $(PROJECT_COPY) -name "$$FILE_SPEC"`; do \
rm $$FILE_NAME; \
echo "# + Removed $$FILE_NAME"; \
done; \
done
# - Removing individual files: $(REMOVE_FILES)
@for FILE_SPEC in $(REMOVE_FILES); do \
for FILE_NAME in `find $(PROJECT_COPY) -name "$$FILE_SPEC"`; do \
if [ -f "$$FILE_NAME" ]; then \
rm $$FILE_NAME; \
echo "# + Removed $$FILE_NAME"; \
fi; \
done; \
done
Structurally, at least, clean_build_directory
isn't
much different from add_snapshot_files
. The same sort
of iteration over a list of files is present (twice, admittedly). The main
differences are in how those files are identified, and what happens to the
individual files during the iteration. Instead of copying a file into
the project-copy directory from the original project-directory, it's removing
files from the project-copy. Identification of the files to be removed is through
the system's find
utility, allowing patterns to be specified in
REMOVE_FILE_TYPES
and specific files in the REMOVE_FILES
list.
The create_snapshot_zip
Target
At this point, all that's remaining to do is to generate the final snapshot
ZIP file, and maybe do a little clean-up (removing the build-directory, since
there's nothing left to do with it):
#create_snapshot_zip:
# Creating snapshot ZIP file:
@cd $(BUILD_DIRECTORY);zip -qr $(BUILD_OUTPUT) $(PROJECT_NAME)
# Removing the build-directory
@rm -fR $(BUILD_DIRECTORY)
Final Check of the snapshot
Target
Since the create_snapshot_zip
target creates the
final ZIP-file in it's final location, the BUILD_ZIP
value that was
originally in the snapshot
target was no longer
needed. It was originally going to be used to generate an intermediary ZIP-file
in the build-directory, but the intermediate step wasn't necessary, so I've
removed it:
snapshot: ZIP_FILE_NAME=$(PROJECT_NAME).zip
snapshot: BUILD_OUTPUT=$(CURRENT_BUILDS)/$(ZIP_FILE_NAME)
snapshot: BUILD_PATHS=$(BUILD_PATHS_SNAP)
snapshot: current_builds build_directory base_all add_snapshot_files clean_build_directory create_snapshot_zip
# Build complete for SNAPSHOT of $(PROJECT_NAME) project:
# + Source ... $(PROJECT_DIRECTORY)/[$(BUILD_PATHS)]
# + Output ... $(BUILD_OUTPUT)
In the interests of showing where things have landed with being able to make
snapshots from a build-process, I'm saving a copy of the current
Makefile
template, before I start tackling any of the local_*
build-targets, that can be downloaded and examined as you see fit. I've also got
a Makefile
in the idic
project now, one that's
slightly further along than what I've shown in this post. It's also
available for download below, as an item in the snapshot that it built.
I don't think I'll pursue the
local_*
builds much further than I
have for now — until I get to a point where I need to be able to
generate a local installation of some sort, I'd have no way to meaningfully test
the accuracy of the process. With where I am in my efforts on the idic
framework so far, that's likely to be a while, a point that I'll think over and
discuss in my next post.
7.9kB
43.4kB
No comments:
Post a Comment