diff --git a/.gitattributes b/.gitattributes index ae8527a..9e3243a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1 @@ -make/** linguist-language=make +*make/** linguist-language=make diff --git a/.make/all_functions b/.make/all_functions new file mode 100644 index 0000000..7982005 --- /dev/null +++ b/.make/all_functions @@ -0,0 +1,9 @@ +include .make/functions/comparison +include .make/functions/directory +include .make/functions/file_system +include .make/functions/library +include .make/functions/list +include .make/functions/path +include .make/functions/recursion +include .make/functions/shell +include .make/functions/variables diff --git a/make/archiver b/.make/archiver similarity index 100% rename from make/archiver rename to .make/archiver diff --git a/.make/commands b/.make/commands new file mode 100644 index 0000000..50fef95 --- /dev/null +++ b/.make/commands @@ -0,0 +1,22 @@ +# Generates a directory creation command. +# Does not fail if the directory already exists. +# Creates all parent directories as needed. +# +# Arguments: +# $(1) = directories to create +# +# Returns: +# command line that creates $(1) +# +mkdir_p = mkdir -p $(1) + +# Generates a file download command. +# +# Arguments: +# $(1) = what to download +# $(2) = where to write downloaded data to +# +# Returns: +# command line that downloads $(1) to $(2) +# +download = curl --output $(2) $(1) diff --git a/.make/compiler b/.make/compiler new file mode 100644 index 0000000..08c3aa9 --- /dev/null +++ b/.make/compiler @@ -0,0 +1 @@ +include .make/compilers/gcc diff --git a/.make/compilers/gcc b/.make/compilers/gcc new file mode 100644 index 0000000..fc7717e --- /dev/null +++ b/.make/compilers/gcc @@ -0,0 +1,79 @@ +# GCC executable +gcc := gcc + +# GCC option variable and function definitions +gcc.options.output = -o $(1) +gcc.options.compile := -c +gcc.options.preprocess := -E +gcc.options.dependency_generation = -MD -MF $(1) $(foreach target,$(2),-MT $(target)) +gcc.options.standard = -std=$(1) +gcc.options.directory.include = -I $(1) +gcc.options.directory.library = -L$(1) +gcc.options.include.macros = $(foreach file,$(1),-imacros $(file)) +gcc.options.dump.macro_names := -dN +gcc.redirect_input_to = - < $(1) +gcc.null_input := $(call gcc.redirect_input_to,/dev/null) + +gcc_dialect_options = $(strip $(call gcc.options.standard,$(or $(1),c99)) $(if $(2),-ffreestanding)) +gcc_warning_options := -Wall -Wextra -Wpedantic +gcc_preprocessor_options = $(call gcc.options.directory.include,$(include_directory)) $(gcc_inhibit_linemarkers_option) + +# Generates a GCC command line that compiles a source file into an object file. +# +# Arguments: +# $(1) = source file +# $(2) = object file +# $(3) = dependency data file +# $(4) = options +# $(5) = GCC executable to use +# +# Returns: +# command line that uses $(5) with the options in $(4) +# to compile $(1) into $(2) and emit dependency data to $(3) +# +define gcc.compile +$(or $(5),$(gcc)) \ +$(4) \ +$(call gcc.options.dependency_generation,$(3),$(2)) \ +$(call gcc.options.output,$(2)) \ +$(gcc.options.compile) $(1) +endef + +# Generates a GCC command line that links object files. +# +# Arguments: +# $(1) = object files +# $(2) = output file +# $(3) = options +# $(4) = GCC executable to use +# +# Returns: +# command line that uses $(4) with the options in $(3) +# to link $(1) into $(2) +# +define gcc.link +$(or $(4),$(gcc)) \ +$(3) \ +$(call gcc.options.output,$(2)) \ +$(1) +endef + +# Generates a GCC command line that lists all defined macros. +# +# Arguments: +# $(1) = files to include +# $(2) = options +# $(3) = GCC executable to use +# +# Returns: +# command line that uses $(3) with the options in $(2) +# to list all macros defined after including the files in $(1) +# +define gcc.list_defined_macros +$(or $(3),$(gcc)) \ +$(gcc.options.preprocess) \ +$(gcc.options.dump.macro_names) \ +$(call gcc.options.include.macros,$(1)) \ +$(2) \ +$(gcc.null_input) +endef diff --git a/.make/configuration b/.make/configuration new file mode 100644 index 0000000..1e28e5d --- /dev/null +++ b/.make/configuration @@ -0,0 +1,2 @@ +configuration.separator ?= - +configuration.current = $(call coalesce,$(configuration),$(configuration.separator)) diff --git a/.make/definitions b/.make/definitions new file mode 100644 index 0000000..4cf622f --- /dev/null +++ b/.make/definitions @@ -0,0 +1,7 @@ +space := +space += + +define \n := + + +endef diff --git a/.make/dependencies b/.make/dependencies new file mode 100644 index 0000000..b197f0f --- /dev/null +++ b/.make/dependencies @@ -0,0 +1,17 @@ +# The wildcard function returns existing files that match its parameters. +# This prevents make from including files that don't exist. +# +# Make tries to rebuild included files, without the wildcard function +# the dependency data files would be generated with every invocation of make, +# even make clean. +# +# References: +# +# https://www.gnu.org/software/make/manual/html_node/Remaking-Makefiles.html +# https://make.mad-scientist.net/papers/advanced-auto-dependency-generation/#include +# https://make.mad-scientist.net/constructed-include-files/ +# + +dependency_file? = $(call boolean,$(and $(call equal?,$(suffix $(1)),.d),$(call file?,$(1)))) + +include $(call find,$(build_dependencies_directory),dependency_file?) diff --git a/.make/file b/.make/file new file mode 100644 index 0000000..3fc8185 --- /dev/null +++ b/.make/file @@ -0,0 +1,36 @@ +# Disable all predefined rules and variables +MAKEFLAGS += --no-builtin-rules --no-builtin-variables + +# Boolean logic in make +include .make/logic + +# Useful variables +include .make/definitions + +# Commands used by the build system +include .make/commands + +# Make functions available to the entire build system +include .make/all_functions + +# Build configuration +include .make/configuration + +# File system structure of the build tree +include .make/structure + +# Compiler and archiver configuration +include .make/compiler +include .make/archiver + +# Rule templates +include $(addprefix .make/rules/,template objects executable libraries run) + +# Project interface +include .make/project + +# Include the user's makefile, if it exists +include $(wildcard make/file) + +# Include all generated dependency files +include .make/dependencies diff --git a/make/functions/comparison b/.make/functions/comparison similarity index 63% rename from make/functions/comparison rename to .make/functions/comparison index 9ae53c2..b94461f 100644 --- a/make/functions/comparison +++ b/.make/functions/comparison @@ -1,5 +1,5 @@ # x - y = 0 ∴ x = y -equal? = $(if $(subst $(1),,$(2)),,=) +equal? = $(if $(subst $(1),,$(2))$(subst $(2),,$(1)),,=) # x - y ≠ 0 ∴ x ≠ y not_equal? = $(if $(call equal?,$(1),$(2)),,≠) diff --git a/.make/functions/directory b/.make/functions/directory new file mode 100644 index 0000000..5e9b31e --- /dev/null +++ b/.make/functions/directory @@ -0,0 +1,10 @@ +# Generates a command that makes sure the given directory exists. +# Generates the empty string if the directory already exists. +# +# $(1) = path to directory to create +# +ensure_directory_exists = $(if $(call directory?,$(1)),,$(call mkdir_p,$(1))) + +# Ensures the existence of the directory where the target of a rule is located in. +# This function references an automatic variable so it should only be used inside recipes. +ensure_target_directory_exists = $(call ensure_directory_exists,$(@D)) diff --git a/.make/functions/file_system b/.make/functions/file_system new file mode 100644 index 0000000..77141e8 --- /dev/null +++ b/.make/functions/file_system @@ -0,0 +1,107 @@ +# Determines whether the given path exists and is a directory. +# Only existing directories contain a "." entry. +# +# Arguments: +# $(1) = path to check +# +# Returns: +# whether $(1) refers to an existing directory +# +# References: +# MadScientist's answer: +# http://stackoverflow.com/a/9456282/512904 +# +directory? = $(if $(1),$(call boolean,$(wildcard $(addsuffix /.,$(1))))) + +# Determines whether the given path exists and is a file. +# Files are defined as everything that is not a directory. +# +# Arguments: +# $(1) = path to check +# +# Returns: +# whether $(1) refers to an existing file +# +file? = $(call boolean,$(and $(wildcard $(1)),$(call not,$(call directory?,$(1))))) + +# Returns a sorted list of unique paths matching the specified pattern. +# +# Arguments: +# $(1) = pattern entries must match +# +# Defaults: +# $(1) = * +# +# Returns: +# list of paths matching $(1) +# +glob = $(sort $(wildcard $(or $(1),$(glob.default)))) + +# By default, glob with match everything except hidden files in the current directory. +glob.default := * + +# Returns a sorted list of unique paths inside the specified directory +# that match the specified pattern. +# +# Arguments: +# $(1) = list of directories to work with +# $(2) = pattern entries must match +# +# Defaults: +# $(1) = . +# $(2) = * +# +# Returns: +# list of paths in $(1) matching $(2) +# +# Example: +# +# # Enumerates all entries in the current directory. +# $(call glob.directory) +# +# Example: +# +# # Enumerates all entries in the current directory that start with ".git". +# $(call glob.directory,,.git*) +# +glob.directory = $(call glob,$(addsuffix /$(or $(2),$(glob.default)),$(or $(1),$(glob.directory.default)))) + +# By default, glob.directory operates on the current directory. +glob.directory.default := . + +# Traverses the file system recursively. +# +# Arguments: +# $(1) = starting directory +# +# Defaults: +# $(1) = . +# +# Returns: +# list with the contents of the tree rooted in $(1) +# +file_system.traverse = $(call recurse,glob.directory,file?,$(or $(1),$(file_system.traverse.default))) + +# By default, file_system.traverse operates on the current directory. +file_system.traverse.default := . + +# Walks the directory and returns all files satisfying the predicate function. +# Behaves just like file_system.traverse if not given a predicate function. +# +# Arguments: +# $(1) = starting directory +# $(2) = optional predicate function +# +# Defaults: +# $(1) = . +# $(2) = true +# +# Returns: +# list containing paths in $(1) that satisfy $(2) +# +# Example: +# +# # Find all source files. +# $(call find,source,file?) +# +find = $(strip $(foreach entry,$(call file_system.traverse,$(1)),$(if $(call $(or $(2),true),$(entry)),$(entry)))) diff --git a/.make/functions/library b/.make/functions/library new file mode 100644 index 0000000..18e545d --- /dev/null +++ b/.make/functions/library @@ -0,0 +1,14 @@ +library.prefix := lib +library.extension.static := .a +library.extension.dynamic := .so + +# Computes the file name for the given library name and type. +# +# Arguments: +# $(1) = library name +# $(2) = linkage type: static | dynamic +# +# Returns: +# the file name for the $(1) library +# +library.file_name = $(addsuffix $(library.extension.$(2)),$(addprefix $(library.prefix),$(1))) diff --git a/.make/functions/list b/.make/functions/list new file mode 100644 index 0000000..ba84cad --- /dev/null +++ b/.make/functions/list @@ -0,0 +1,42 @@ +# Maps the given function to all values in the given list. +# +# Arguments: +# $(1) = function to map to values +# $(2) = list of values +# +# Returns: +# results of calling the $(1) function on each value of the $(2) list +# +map = $(foreach value,$(strip $(2)),$(call $(1),$(value))) + +# Removes the first element of the list and returns the rest. +# +# Arguments: +# $(1) = list of values +# +# Returns: +# the list without the first element +# +rest = $(wordlist 2,$(words $(1)),$(1)) + +# Inverts the order of the elements of the list. +# +# Arguments: +# $(1) = list of values +# +# Returns: +# the reversed list reversed +# +reverse = $(if $(word 2,$(1)),$(call reverse,$(call rest,$(1))) $(firstword $(1)),$(1)) + +# Joins all elements of the given list. +# +# Arguments: +# $(1) = list of values +# $(2) = optional separator +# +# Returns: +# elements of $(1) combined into one +# +coalesce* = $(if $(2),$(addprefix $(addsuffix $(3),$(1)),$(2)),$(1)) +coalesce = $(if $(word 2,$(1)),$(call $(0)*,$(firstword $(1)),$(call $(0),$(call rest,$(1)),$(2)),$(2)),$(1)) diff --git a/.make/functions/path b/.make/functions/path new file mode 100644 index 0000000..afd473e --- /dev/null +++ b/.make/functions/path @@ -0,0 +1,72 @@ +# File system path separator. Specific to each operating system. +path.separator ?= / + +# Forms a new path by joining all the given elements. +# +# Arguments: +# $(1) = list of paths to join +# $(2) = path separator +# +# Defaults: +# $(2) = $(path.separator) +# +# Returns: +# all elements in $(1) joined into one path +# +path.join = $(call coalesce,$(1),$(or $(2),$(path.separator))) + +# Converts a source file path to its corresponding object file path. +# +# Arguments: +# $(1) = path to source file +# +# Returns: +# path to the object file +# +to_object = $(addprefix $(build_objects_directory)/,$(addsuffix .o,$(basename $(1)))) + +# Converts the path to a source file to the path to its corresponding dependency data file +# +# Arguments: +# $(1) = path to a source file +# +# Returns: +# path to the dependency file +# +to_dependency = $(addprefix $(build_dependencies_directory)/,$(addsuffix .d,$(basename $(1)))) + +# Converts the path to a library to the path to its corresponding target file. +# +# Arguments: +# $(1) = path to a library +# +# Variables: +# $(linkage) = linkage type: static | dynamic +# +# Returns: +# path to the library target +# +to_library = $(addprefix $(build_libraries_directory)/,$(1)) + +# Converts a library name to the path to its corresponding library file. +# +# Arguments: +# $(1) = library name +# +# Variables: +# $(linkage) = linkage type: static | dynamic +# +# Returns: +# path to the library target +# +as_library = $(call to_library,$(call library.file_name,$(1),$(linkage))) + +# Converts an executable name to its corresponding executable file path. +# +# Arguments: +# $(1) = name of the executable +# +# Returns: +# path to the executable file +# +to_executable = $(addprefix $(build_executables_directory)/,$(1)) diff --git a/.make/functions/recursion b/.make/functions/recursion new file mode 100644 index 0000000..d3e4765 --- /dev/null +++ b/.make/functions/recursion @@ -0,0 +1,18 @@ +# Recursively calls a function on lists and aggregates the results. +# +# The base case predicate function takes a list element as argument +# and determines whether it is a base case. +# If an element is a base case, it is returned in the result. +# If an element is not a base case, it is returned in the result +# along with the results of recursing with a new list +# generated by applying the given function to the element. +# +# Arguments: +# $(1) = recursive element generator function +# $(2) = base case predicate function +# $(3) = initial list of values +# +# Returns: +# list containing all recursively computed values +# +recurse = $(foreach x,$(3),$(if $(call $(2),$(x)),$(x),$(x) $(call recurse,$(1),$(2),$(call $(1),$(x))))) diff --git a/make/functions/shell b/.make/functions/shell similarity index 100% rename from make/functions/shell rename to .make/functions/shell diff --git a/.make/functions/variables b/.make/functions/variables new file mode 100644 index 0000000..95bbd78 --- /dev/null +++ b/.make/functions/variables @@ -0,0 +1,146 @@ +# Accesses the value associated with the given name. +# Useful for functional composition. +# +# Arguments: +# $(1) = name of the variable to dereference +# +# Variables: +# $($(1)) = the value that will be accessed +# +# Returns: +# the valuable of the variable +# +dereference = $($(1)) +* = $(call dereference,$(1)) + +# Tests whether the given variable is undefined. +# +# Arguments: +# $(1) = name of the variable +# +# Returns: +# whether the $(1) variable is undefined +# +undefined? = $(call boolean,$(call equal?,undefined,$(flavor $(1)))) + +# Tests whether the given variable is defined. +# +# Arguments: +# $(1) = name of the variable +# +# Returns: +# whether the $(1) variable is defined +# +defined? = $(call not,$(call undefined?,$(1))) + +# Resolves a composite variable name. +# +# Arguments: +# $(1) = list of variable name components in order +# $(2) = separator +# +# Defaults: +# $(2) = . +# +# Returns: +# the name of the variable +# +resolve.name = $(subst $(space),$(or $(2),.),$(strip $(1))) + +# Resolves the name of the first defined variable in a list of namespaces. +# A namespace is the first component of a composite variable name. +# +# Arguments: +# $(1) = list of namespaces in order +# $(2) = variable to resolve +# $(3) = separator +# +# Defaults: +# $(3) = . +# +# Returns: +# the name of the first defined variable $(1) in the $(2) namespaces +# +resolve.name.first* = $(if $(call defined?,$(4)),$(4),$(call resolve.name.first,$(call rest,$(1)),$(2),$(3))) +resolve.name.first = $(if $(1),$(call $(0)*,$(1),$(2),$(3),$(call resolve.name,$(firstword $(1)) $(2),$(3))),$(call resolve.name,$(2),$(3))) + +# Resolves a composite variable name and retrieves its value. +# +# Arguments: +# $(1) = list of variable name components in order +# $(2) = separator +# +# Defaults: +# $(2) = . +# +# Returns: +# the value bound to the variable +# +resolve.value = $($(call resolve.name,$(1),$(2))) + +# Resolves the value of the first defined variable in a list of namespaces. +# A namespace is the first component of a composite variable name. +# +# Arguments: +# $(1) = list of namespaces in order +# $(2) = variable to resolve +# $(3) = separator +# +# Defaults: +# $(3) = . +# +# Returns: +# the value of the first defined variable $(1) in the $(2) namespaces +# +resolve.value.first = $($(call resolve.name.first,$(1),$(2),$(3))) + +# Resolves the value of the first defined variable in a list of namespaces +# in addition to the value of the variable in the global namespace. +# A namespace is the first component of a composite variable name. +# +# Arguments: +# $(1) = list of namespaces +# $(2) = variable to resolve +# $(3) = separator +# +# Defaults: +# $(3) = . +# +# Returns: +# the the value of $(1) in the global namespace +# and the value the first defined $(1) variable inside all $(2) namespaces +# +resolve.stack+ = $(strip $(call resolve.value,global $(2),$(3)) $(call resolve.value.first,$(1),$(2),$(3))) + +# Resolves the value of the first defined variable in a list of namespaces and the global namespace. +# A namespace is the first component of a composite variable name. +# +# Arguments: +# $(1) = list of namespaces +# $(2) = variable to resolve +# $(3) = separator +# +# Defaults: +# $(3) = . +# +# Returns: +# the value the first defined $(1) variable inside all $(2) namespaces +# or the value of $(1) in the global namespace +# +resolve.stack- = $(call resolve.value.first,$(1) global,$(2),$(3)) + +# Resolves a variable in a list of namespaces and retrieves their values. +# A namespace is the first component of a composite variable name. +# +# Arguments: +# $(1) = list of namespaces +# $(2) = variable to resolve +# $(3) = separator +# +# Defaults: +# $(3) = . +# +# Returns: +# the values bound to the variable $(1) inside all the namespaces in $(2) +# +resolve.all = $(strip $(foreach ns,$(1),$(call resolve.value,$(ns) $(2),$(3)))) diff --git a/.make/logic b/.make/logic new file mode 100644 index 0000000..002adde --- /dev/null +++ b/.make/logic @@ -0,0 +1,46 @@ +# Definitions of boolean true and false. +# In Makefiles, the empty string is false and everything else is true. +# +true := ✓ +false := + +# Converts the given data to a truth value. +# +# Arguments: +# $(1) = the value to test +# +# Returns: +# $(false) if value is empty, $(true) otherwise +# +boolean = $(if $(1),$(true),$(false)) + +# Inverts the truth value of the given data. +# +# Arguments: +# $(1) = the value to test +# +# Returns: +# $(true) if value is empty, $(false) otherwise +# +not = $(if $(1),$(false),$(true)) + +# User-friendly symbols for true and false. +# +# The boolean symbols provide textual representations for truth values +# because it is inconvenient to output false values as empty text. +# +# Both symbols are logically true. +# +true.symbol := $(true) +false.symbol := ✗ + +# Converts the given data to its symbolic truth value. +# The result of this function is never logically false. +# +# Arguments: +# $(1) = the value to test +# +# Returns: +# $(false.symbol) if value is empty, $(true.symbol) otherwise +# +boolean.symbol = $(if $(1),$(true.symbol),$(false.symbol)) diff --git a/.make/project b/.make/project new file mode 100644 index 0000000..cc495ce --- /dev/null +++ b/.make/project @@ -0,0 +1,396 @@ +project = $(call project.evaluate,$(1)) + +# Generates and evaluates rules for the given project. +# +# Arguments: +# $(1) = name of the project +# +project.evaluate = $(eval $(call project.generate,$(1))) + +# Generates rules for the given project. +# +# Arguments: +# $(1) = the project +# +# Variables: +# $($(1).targets) = targets to define rules for +# $($(1).targets.default) = used as the default target if specified +# +define project.generate + +# $(0) +# $(1) + +$(call project.targets,$($(1).targets),$(1)) + +.DEFAULT_GOAL := $(or $($(1).targets.default),$(firstword $($(1).targets))) + +.PHONY : all +all : $($(1).targets) + +.PHONY : clean +clean: + rm -rf $(build_directory)/ + +# end $(0) + +endef + +# Generates rules for the given targets. +# +# Arguments: +# $(1) = the project's targets +# $(2) = build stack +# +project.targets = $(foreach target,$(1),$(call project.target,$(target),$(target) $(2))) + +# Generates rules appropriate for the given target's type. +# +# Arguments: +# $(1) = the project's target +# $(2) = build stack +# +# Variables: +# $($(1).type) = the type of the target: object | executable | library | recipe +# +project.target = $(call project.target.$($(1).type),$(1),$(2)) + +# Generates rules for compiling the given sources into objects. +# +# Generates a phony target to refer to the objects. +# +# Arguments: +# $(1) = the project's object target +# $(2) = build stack +# +# Variables: +# $($(1).sources) = source files to compile +# +define project.target.object + +# $(0) +# $(1) +# $(2) + +$(call project.target.object.rules,$(1),$(2)) + +.PHONY : $(1) +$(1) : $(call to_object,$($(1).sources)) + +# end $(0) + +endef + +# Generates rules for compiling the given sources into objects and dependency data files. +# +# Arguments: +# $(1) = the project's object target +# $(2) = build stack +# +# Variables: +# $($(1).sources) = source files to compile +# $($(1).dependencies) = additional dependencies for the objects +# +define project.target.object.rules + +# $(0) +# $(1) +# $(2) + +$(foreach source,$($(1).sources),$(call $(0).file,$(source),$(call to_object,$(source)) $(2),$($(1).dependencies))) + +# end $(0) + +endef + +# Generates rules for compiling the given source into object and dependency data files. +# +# Arguments: +# $(1) = the source file +# $(2) = build stack +# $(3) = additional dependencies for the object +# +define project.target.object.rules.file + +# $(0) +# $(1) +# $(2) +# $(3) + +$(call $(0)*,$(1),$(2),$(3),$(call resolve.stack-,$(2),compiler)) + +# end $(0) + +endef + +# Arguments: +# $(1) = the source file +# $(2) = build stack +# $(3) = additional dependencies for the object +# $(4) = compiler to use +# +define project.target.object.rules.file* + +# $(0) +# $(1) +# $(2) +# $(3) +# $(4) + +$(call $(0)*,$(1),$(3),$(4),$(call resolve.stack+,$(2),compiler $(4) options),$(call resolve.stack-,$(2),compiler $(4))) + +# end $(0) + +endef + +# Arguments: +# $(1) = the source file +# $(2) = additional dependencies for the object +# $(3) = compiler to use +# $(4) = compiler options +# $(5) = compiler executable +# +define project.target.object.rules.file** + +# $(0) +# $(1) +# $(2) +# $(3) +# $(4) +# $(5) + +$(call generate_object_rule,$(1),$(2),$(3),$(4),$(5)) + +# end $(0) + +endef + +# Generates rules for compiling the given sources into objects +# and for compiling the resulting objects into an executable. +# +# Generates a phony target to refer to the executable. +# Generates a phony target to run the executable. +# +# Arguments: +# $(1) = the project's executable target +# $(2) = build stack +# +# Variables: +# $($(1).sources) = source files to compile +# +define project.target.executable + +# $(0) +# $(1) +# $(2) + +$(call project.target.executable.rules,$(1),$(2)) + +.PHONY : $(1) +$(1) : $(call to_executable,$(1)) + +$(call generate_run_executable_rule,$(1)) + +# end $(0) + +endef + +# Generates rules for compiling the given sources into objects +# and for compiling the resulting objects into an executable. +# +# Arguments: +# $(1) = the project's executable target +# $(2) = build stack +# +# Variables: +# $($(1).sources) = source files to compile +# $($(1).dependencies) = additional dependencies for the executable target +# +define project.target.executable.rules + +# $(0) +# $(1) +# $(2) + +$(call project.target.object.rules,$(1),$(2)) + +$(call $(0).file,$(1),$(2),$($(1).sources),$($(1).dependencies),$(call resolve.stack-,$(2),linker)) + +# end $(0) + +endef + +# Arguments: +# $(1) = the project's executable target +# $(2) = build stack +# $(3) = sources +# $(4) = additional dependencies +# $(5) = linker to use +# +define project.target.executable.rules.file + +# $(0) +# $(1) +# $(2) +# $(3) +# $(4) +# $(5) + +$(call $(0)*,$(1),$(3),$(4),$(5),$(call resolve.stack+,$(2),linker $(5) options),$(call resolve.stack-,$(2),linker $(5))) + +# end $(0) + +endef + +# Arguments: +# $(1) = the project's executable target +# $(2) = sources +# $(3) = additional dependencies +# $(4) = compiler to use +# $(5) = compiler options +# $(6) = compiler executable +# +define project.target.executable.rules.file* + +# $(0) +# $(1) +# $(2) +# $(3) +# $(4) +# $(5) +# $(6) + +$(call generate_executable_rule,$(1),$(2),$(3),$(4),$(5),$(6)) + +# end $(0) + +endef + +# Generates rules for compiling the given sources into objects +# and for compiling the resulting objects into a library. +# +# Generates a phony target to refer to the library. +# +# Arguments: +# $(1) = the project's library target +# $(2) = build stack +# +# Variables: +# $($(1).sources) = source files to compile +# +define project.target.library + +# $(0) +# $(1) +# $(2) + +$(call project.target.library.rules,$(1),$(2)) + +.PHONY : $(1) +$(1) : $(call as_library,$($(1).name)) + +# end $(0) + +endef + +# Generates rules for the given library. +# +# Arguments: +# $(1) = the project's library target +# $(2) = build stack +# +# Variables: +# $($(1).name) = the name of the library +# $($(1).sources) = source files to compile +# $($(1).dependencies) = additional dependencies for the library +# +define project.target.library.rules + +# $(0) +# $(1) +# $(2) + +$(call project.target.object.rules,$(1),$(2)) + +$(call $(0).file,$(1),$(2),$($(1).name),$(call to_object,$($(1).sources)),$($(1).dependencies),$(call resolve.stack-,$(2),linker)) + +# end $(0) + +endef + +# Generates rules for the given library. +# +# Arguments: +# $(1) = the project's library target +# $(2) = build stack +# $(3) = library name +# $(4) = library objects +# $(5) = additional dependencies +# $(6) = linker to use +# +define project.target.library.rules.file + +# $(0) +# $(1) +# $(2) +# $(3) +# $(4) +# $(5) +# $(6) + +$(call $(0)*,$(3),$(4),$(5),$(6),$(call resolve.stack+,$(2),linker $(6) options),$(call resolve.stack-,$(2),linker $(6))) + +# end $(0) + +endef + +# Arguments: +# $(1) = library name +# $(2) = library objects +# $(3) = additional dependencies +# $(4) = linker to use +# $(5) = linker options +# $(6) = linker executable +# +define project.target.library.rules.file* + +# $(0) +# $(1) +# $(2) +# $(3) +# $(4) +# $(5) +# $(6) + +$(call library_rule_template,$(1),$(2),$(3),$(4),$(5),$(6)) + +# end $(0) + +endef + +# Includes the given recipe in the generated rules. +# +# Generates a phony target to refer to the specified targets. +# +# Arguments: +# $(1) = the project's recipe target +# $(2) = build stack +# +# Variables: +# $($(1).recipe) = the recipe to include in the generated rules +# $($(1).targets) = the prerequisites of the phony target +# +define project.target.recipe + +# $(0) +# $(1) +# $(2) + +$($(1).recipe) + +.PHONY : $(1) +$(1) : $($(1).targets) + +# end $(0) + +endef diff --git a/.make/rules/executable b/.make/rules/executable new file mode 100644 index 0000000..8aea164 --- /dev/null +++ b/.make/rules/executable @@ -0,0 +1,39 @@ +# Rule template for executables. +# +# The reason why the executables depend on their dependency data file +# is to trigger the generation of their dependency data when they are updated. +# Once generated, future executions of make will include and benefit from the dependency data. +# Dependency data file generation rules are defined in .make/targets/dependencies. +# +# Arguments: +# $(1) = path to the executable file +# $(2) = paths to the object files +# $(3) = additional dependencies +# $(4) = compiler +# $(5) = compiler options +# $(6) = compiler executable +# +# Returns: +# rules for compiling the objects in $(2) into the executable $(1) +# +# References: +# +# https://make.mad-scientist.net/papers/advanced-auto-dependency-generation/#depdelete +# +executable_rule_template = $(call rule_template,$(1),$(call $(4).link,$(2),$(1),$(5),$(6)),$(2) $(3),$(dir $(1))) + +# Computes the parameters for and evaluates the executable rule template. +# +# Arguments: +# $(1) = executable name +# $(2) = executable sources +# $(3) = additional dependencies +# $(4) = compiler +# $(5) = compiler options +# $(6) = compiler executable +# +# Returns: +# rules for compiling the objects generated from the sources in $(2) +# into the executable named $(1) +# +generate_executable_rule = $(call executable_rule_template,$(call to_executable,$(1)),$(call to_object,$(2)),$(3),$(4),$(5),$(6)) diff --git a/.make/rules/libraries b/.make/rules/libraries new file mode 100644 index 0000000..52dc11e --- /dev/null +++ b/.make/rules/libraries @@ -0,0 +1,59 @@ +# Rule template for static libraries. +# +# Arguments: +# $(1) = path to static library +# $(2) = list of object files +# $(3) = additional dependencies +# +# Returns: +# rules for generating static library $(1) from the objects in $(2) +# +static_library_rule_template = $(call rule_template,$(1),$(call archiver.create,$(1),$(2)),$(2) $(3),$(dir $(1))) + +# Rule template for dynamic libraries. +# +# Arguments: +# $(1) = path to dynamic library +# $(2) = list of object files +# $(3) = additional dependencies +# $(4) = linker +# $(5) = linker options +# $(6) = linker executable +# +# Returns: +# rules for generating dynamic library $(1) from the objects in $(2) +# +dynamic_library_rule_template = $(call rule_template,$(1),$(call $(4).link,$(2),$(1),$(5),$(6)),$(2) $(3),$(dir $(1))) + +# Rule template for libraries corresponding to the current linkage. +# +# Arguments: +# $(1) = library name +# $(2) = list of object files +# $(3) = additional dependencies +# $(4) = linker +# $(5) = linker options +# $(6) = linker executable +# +# Variables: +# $(linkage) = linkage type: static | dynamic +# +# Returns: +# rules for generating library $(1) from the objects in $(2) +# +library_rule_template = $(call $(linkage)_library_rule_template,$(call as_library,$(1)),$(2),$(3),$(4),$(5),$(6)) + +# Evaluates a library rule template for the current linkage. +# +# Arguments: +# $(1) = library name +# $(2) = list of object files +# $(3) = additional dependencies +# +# Variables: +# $(linkage) = linkage type: static | dynamic +# +# Returns: +# ε +# +evaluate_library_rule = $(eval $(call library_rule_template,$(1),$(2),$(3))) diff --git a/.make/rules/objects b/.make/rules/objects new file mode 100644 index 0000000..7f219ab --- /dev/null +++ b/.make/rules/objects @@ -0,0 +1,32 @@ +# Rule template for object files. +# +# The reason why objects depend on their dependency data file +# is to trigger the generation of their dependency data when they are updated. +# Once generated, future executions of make will include and benefit from the dependency data. +# Dependency data file generation rules are defined in .make/targets/dependencies. +# +# Arguments: +# $(1) = path to source file +# $(2) = path to object file +# $(3) = path to dependency data file +# $(4) = additional dependencies +# $(5) = compiler +# $(6) = compiler options +# $(7) = compiler executable +# +# References: +# +# https://make.mad-scientist.net/papers/advanced-auto-dependency-generation/#depdelete +# +object_rule_template = $(call rule_template,$(2),$(call $(5).compile,$(1),$(2),$(3),$(6),$(7)),$(1) $(4),$(dir $(2) $(3))) + +# Computes the parameters for and evaluates the object rule template. +# +# Arguments: +# $(1) = path to source file +# $(2) = additional dependencies +# $(3) = compiler +# $(4) = compiler options +# $(5) = compiler executable +# +generate_object_rule = $(call object_rule_template,$(1),$(call to_object,$(1)),$(call to_dependency,$(1)),$(2),$(3),$(4),$(5)) diff --git a/.make/rules/run b/.make/rules/run new file mode 100644 index 0000000..09036c8 --- /dev/null +++ b/.make/rules/run @@ -0,0 +1,10 @@ +configure_dynamic_loader = $(if $(call equal?,$(linkage),dynamic),LD_LIBRARY_PATH=$(build_libraries_directory)) + +define run_executable_rule_template +.PHONY : run-$(1) + +run-$(1) : $(2) + $(call configure_dynamic_loader) $(2) +endef + +generate_run_executable_rule = $(call run_executable_rule_template,$(1),$(call to_executable,$(1))) diff --git a/.make/rules/template b/.make/rules/template new file mode 100644 index 0000000..2c73b86 --- /dev/null +++ b/.make/rules/template @@ -0,0 +1,17 @@ +# Common rule template that all generated rules follow. +# All rules ensure that their target's directory exist. +# +# Arguments: +# $(1) = path to target file +# $(2) = command that generates $(1) +# $(3) = optional dependencies list +# $(4) = directories to create +# +# Returns: +# generated rule text for the given parameters +# +define rule_template +$(1) : $(3) + $(call mkdir_p,$(4)) + $(2) +endef diff --git a/.make/structure b/.make/structure new file mode 100644 index 0000000..ccf349c --- /dev/null +++ b/.make/structure @@ -0,0 +1,64 @@ +# Directories for build artifacts +build_directory ?= build +build_root_directory = $(call path.join,$(build_directory) $(configuration.current)) + +build_objects_directory = $(call path.join,$(build_root_directory) objects) + +build_libraries_directory = $(call path.join,$(build_root_directory) libraries) + +build_executables_directory = $(call path.join,$(build_root_directory) executables) + +build_dependencies_directory = $(call path.join,$(build_root_directory) dependencies) + +# If the build directory is a symbolic link to another directory, +# ensure the existence of that directory. +build_directory_link := $(shell readlink $(build_directory)) +$(if $(build_directory_link),$(call execute,$(call ensure_directory_exists,$(build_directory_link)))) + +# build +# └── x86_64 +# ├── dependencies +# │   ├── examples +# │   │   ├── hello-world.d +# │   │   └── ... +# │   └── source +# │   ├── arch +# │   │   └── x86_64 +# │   │   └── system_call.d +# │   └── system_calls +# │   ├── exit.d +# │   └── ... +# ├── examples +# │   ├── dynamic +# │   │   ├── hello-world +# │   │   └── ... +# │   └── static +# │   ├── hello-world +# │   └── ... +# ├── libraries +# │   ├── liblinux.a +# │   ├── liblinux.so +# │   ├── liblinux.specs +# │   └── start +# │   ├── liblinux_start.o +# │   └── _start.o +# ├── objects +# │   ├── dynamic +# │   │   ├── arch +# │   │   │   └── x86_64 +# │   │   │   └── system_call.o +# │   │   └── system_calls +# │   │   ├── exit.o +# │   │   └── ... +# │   └── static +# │   ├── arch +# │   │   └── x86_64 +# │   │   └── system_call.o +# │   └── system_calls +# │   ├── exit.o +# │   └── ... +# └── scripts +# ├── liblinux-gcc +# ├── system-calls.available +# ├── system-calls.implemented +# └── system-calls.missing diff --git a/GNUmakefile b/GNUmakefile index caa4a9d..4aec272 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -1 +1 @@ -include make/file +include .make/file diff --git a/make/all_functions b/make/all_functions deleted file mode 100644 index 290f8ee..0000000 --- a/make/all_functions +++ /dev/null @@ -1,4 +0,0 @@ -include make/functions/comparison -include make/functions/directory -include make/functions/path -include make/functions/shell diff --git a/make/all_scripts b/make/all_scripts deleted file mode 100644 index 933cdbf..0000000 --- a/make/all_scripts +++ /dev/null @@ -1,10 +0,0 @@ -# Project scripts -gcc_specs_script := $(scripts_directory)/$(project).specs.sh -gcc_wrapper_script := $(scripts_directory)/$(project)-gcc.sh - -# Scripts must be downloaded from the kernel's git repository -download = curl --output $(1) $(2) -linux_script_url = https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/plain/scripts/$(1) -download_linux_script = $(call download,$(1),$(call linux_script_url,$(notdir $(1)))) - -include make/scripts/checkpatch diff --git a/make/all_targets b/make/all_targets deleted file mode 100644 index 69a3575..0000000 --- a/make/all_targets +++ /dev/null @@ -1,26 +0,0 @@ -# Object files resulting from the compilation of translation units -include make/targets/objects - -# The main build targets are the static and dynamic libraries -include make/targets/static_library -include make/targets/dynamic_library - -# Targets for the liblinux process startup files -include make/targets/start - -# GCC wrapper script and the specs file used by it -include make/targets/gcc_wrapper - -# Targets for library usage examples -include make/targets/examples - -# Targets for dependency data -include make/targets/dependencies - -# System call lists -include make/targets/system_calls/available -include make/targets/system_calls/implemented -include make/targets/system_calls/missing - -# Phony targets -include make/targets/all_phony diff --git a/make/scripts/checkpatch b/make/checkpatch similarity index 70% rename from make/scripts/checkpatch rename to make/checkpatch index fcc28c5..a4823f4 100644 --- a/make/scripts/checkpatch +++ b/make/checkpatch @@ -1,7 +1,9 @@ -# Integration with the checkpatch.pl script checkpatch.pl := $(scripts_linux_directory)/checkpatch.pl checkpatch_data_files := $(addprefix $(dir $(checkpatch.pl)),const_structs.checkpatch spelling.txt) +linux_script_url = https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/plain/scripts/$(1) +download_linux_script = $(call download,$(call linux_script_url,$(notdir $(1))),$(1)) + $(checkpatch.pl): $(checkpatch_data_files) $(call download_linux_script,$@) chmod +x $@ @@ -9,7 +11,7 @@ $(checkpatch.pl): $(checkpatch_data_files) $(checkpatch_data_files): $(call download_linux_script,$@) -phony_targets += checkpatch +.PHONY : checkpatch checkpatch: $(checkpatch.pl) find $(include_directory) $(source_directory) $(start_directory) $(examples_directory) \ -type f \ diff --git a/make/compiler b/make/compiler deleted file mode 100644 index f8aaaf6..0000000 --- a/make/compiler +++ /dev/null @@ -1,43 +0,0 @@ -include make/compilers/gcc - -default_compiler := gcc -compiler := $(default_compiler) - -compiler_common_options := $($(compiler)_common_options) - -compiler_library_search_options = $($(compiler)_library_directory_option) -compiler_code_generation_options = $($(compiler)_code_generation_options) -compiler_compile_option := $($(compiler)_compile_option) -compiler_shared_library_option := $($(compiler)_shared_library_option) -compiler_nostdlib_option := $($(compiler)_nostdlib_option) -compiler_output_option = $($(compiler)_output_option) -compiler_link_option = $($(compiler)_link_option) - -define compiler.compile_object -$(compiler) \ -$(compiler_common_options) \ -$(compiler_nostdlib_option) \ -$(call compiler_code_generation_options,$(3)) \ -$(call compiler_output_option,$(1)) \ -$(compiler_compile_option) $(2) -endef - -define compiler.compile_startup_object -$(compiler) \ -$(compiler_common_options) \ -$(compiler_nostdlib_option) \ -$(call compiler_output_option,$(1)) \ -$(compiler_compile_option) $(2) -endef - -define compiler.compile_dynamic_library -$(compiler) \ -$(compiler_common_options) \ -$(compiler_nostdlib_option) \ -$(compiler_code_generation_options) \ -$(compiler_shared_library_option) \ -$(call compiler_output_option,$(1)) \ -$(2) -endef - -compiler.generate_dependency_data = $($(compiler).generate_dependency_data) diff --git a/make/compilers/gcc b/make/compilers/gcc deleted file mode 100644 index 5fdd3b5..0000000 --- a/make/compilers/gcc +++ /dev/null @@ -1,63 +0,0 @@ -# GCC executable -gcc := gcc - -# GCC option variable and function definitions -gcc_library_directory_option = -L$(1) -gcc_compile_option := -c -gcc_preprocess_option := -E -gcc_inhibit_linemarkers_option := -P -gcc_include_macros_option = -imacros $(1) -gcc_list_macro_names_option := -dN -gcc_shared_library_option := -shared -gcc_nostdlib_option := -nostdlib -gcc_output_option = -o $(1) -gcc_static_option = $(if $(call equal?,$(1),static),-static) -gcc_link_option = -l $(1) -gcc_specs_option = -specs=$(1) - -gcc_code_generation_options = $(if $(call equal?,$(1),static),-fno-PIC,-fPIC) -gcc_dependency_generation_options = -M -MF $(1) $(foreach target,$(2),-MT $(target)) - -# Common GCC options -gcc_dialect_options := -ansi -ffreestanding -gcc_warning_options := -Wall -Wextra -Wpedantic -gcc_preprocessor_options := -I $(include_directory) $(gcc_inhibit_linemarkers_option) -gcc_optimization_options := -Os -fno-strict-aliasing -gcc_instrumentation_options := -fno-stack-protector - -define gcc_common_options -$(gcc_dialect_options) \ -$(gcc_warning_options) \ -$(gcc_preprocessor_options) \ -$(gcc_optimization_options) \ -$(gcc_instrumentation_options) -endef - -# GCC command line generation functions -define gcc.compile_example -$(gcc_wrapper) \ -$(gcc_common_options) \ -$(call gcc_static_option,$(3)) \ -$(call gcc_output_option,$(1)) \ -$(2) -endef - -define gcc.generate_dependency_data -$(gcc) \ -$(gcc_common_options) \ -$(call gcc_dependency_generation_options,$(1),$(3) $(1)) \ -$(2) -endef - -redirect_input_to = < $(1) -gcc_read_from_standard_input := - -gcc_null_input := $(gcc_read_from_standard_input) $(call redirect_input_to,/dev/null) - -define gcc.list_defined_macros -$(gcc) \ -$(gcc_common_options) \ -$(gcc_preprocess_option) \ -$(gcc_list_macro_names_option) \ -$(call gcc_include_macros_option,$(1)) \ -$(gcc_null_input) -endef diff --git a/make/examples b/make/examples deleted file mode 100644 index 578718c..0000000 --- a/make/examples +++ /dev/null @@ -1 +0,0 @@ -examples := $(basename $(notdir $(sources_examples))) diff --git a/make/file b/make/file index 54fb873..9eb50d6 100644 --- a/make/file +++ b/make/file @@ -1,25 +1,90 @@ -# Make functions available to the entire build system -include make/all_functions +architecture ?= x86_64 +linkage ?= static +compiler ?= gcc -# Project information and file system structure -include make/project +configuration := $(architecture) $(linkage) $(compiler) + +option.PIC = $(if $(call equal?,$(linkage),static),-fno-PIC,-fPIC) +option.linkage = $(if $(call equal?,$(linkage),static),-static,-shared) + +# Project file system structure include make/structure -# Integration with project shell scripts -include make/all_scripts +global.compiler.gcc.options := -std=c99 -ffreestanding \ + -Wall -Wextra -Wpedantic \ + -I $(include_directory) \ + -Os -fno-strict-aliasing -fno-stack-protector + +liblinux.targets := + +liblinux.compiler := $(compiler) +liblinux.compiler.gcc.options := $(option.PIC) + +liblinux.linker := $(compiler) +liblinux.linker.gcc.options := $(option.linkage) + +# System call library +liblinux.targets += library +library.type := library +library.name := linux +library.sources := $(call find,$(source_directory),file?) + +library.linker.gcc.options := $(option.linkage) -nostdlib + +# Process startup code +liblinux.targets += startfiles +startfiles.type := object +startfiles.sources := $(call find,$(start_architecture_directory),file?) + +startfiles.compiler.gcc.options := -fno-PIC +startfiles.linker.gcc.options := -static + +$(call to_object,$(start_architecture_directory)/_start.S).gcc.options := $(startfiles.compiler.gcc.options) -P + +# GCC linker specification file and wrapper script +# Makes it easy to build programs using liblinux +gcc_specs_script := $(scripts_directory)/liblinux.specs.sh +gcc_wrapper_script := $(scripts_directory)/liblinux-gcc.sh + +gcc_wrapper := $(call to_executable,liblinux-gcc) +gcc_specs := $(call to_library,liblinux.specs) + +liblinux.targets += liblinux-gcc +liblinux-gcc.type := recipe +liblinux-gcc.targets := $(gcc_wrapper) + +define liblinux-gcc.recipe := + +$(gcc_wrapper) : $(gcc_specs) $(gcc_wrapper_script) + $(call mkdir_p,$(dir $(gcc_wrapper))) + $(gcc_wrapper_script) $(gcc_specs) > $(gcc_wrapper) + chmod +x $(gcc_wrapper) + +$(gcc_specs) : $(gcc_specs_script) $(call as_library,linux) $(call to_object,$(startfiles.sources)) + $(call mkdir_p,$(dir $(gcc_specs))) + $(gcc_specs_script) $(build_libraries_directory) $(call to_object,$(startfiles.sources)) > $(gcc_specs) + +endef + +# Library usage examples +define examples_template + +liblinux.targets += $(1) + +$(1).type := executable +$(1).sources := $(2) +$(1).dependencies := $(gcc_wrapper) + +$(1).compiler.gcc := $(gcc_wrapper) +$(1).linker.gcc := $(gcc_wrapper) + +endef -# Compiler and archiver configuration -include make/compiler -include make/archiver +$(foreach example,$(call glob,$(examples_directory)/*.c),$(eval $(call examples_template,$(basename $(notdir $(example))),$(example)))) -# List of sources, headers, examples, objects and targets -include make/sources -include make/headers -include make/examples -include make/objects -include make/all_targets +$(call project,liblinux) -# Special variables +include make/system_call_lists -.DEFAULT_GOAL := libraries -.PHONY: $(phony_targets) +# Linux kernel checkpatch script integration +include make/checkpatch diff --git a/make/functions/directory b/make/functions/directory deleted file mode 100644 index bc21c4f..0000000 --- a/make/functions/directory +++ /dev/null @@ -1,26 +0,0 @@ -# Command to use to create directories. -# Should not fail if the directory already exists, and create its parent directories as needed. -mkdir_p := mkdir -p - -# Determines whether the given path exists and is a directory. -# -# Only existing directories contain a "." entry. -# -# $(1) = path to check -# -# References: -# MadScientist's answer: -# http://stackoverflow.com/a/9456282/512904 -# -directory? = $(wildcard $(1)/.) - -# Generates a command that makes sure the given directory exists. -# Generates the empty string if the directory already exists. -# -# $(1) = path to directory to create -# -ensure_directory_exists = $(if $(call directory?,$(1)),,$(mkdir_p) $(1)) - -# Ensures the existence of the directory where the target of a rule is located in. -# This function references an automatic variable so it should only be used inside recipes. -ensure_target_directory_exists = $(call ensure_directory_exists,$(@D)) diff --git a/make/functions/path b/make/functions/path deleted file mode 100644 index 2748e14..0000000 --- a/make/functions/path +++ /dev/null @@ -1,35 +0,0 @@ -# Converts a source file path to its corresponding object file path. -# Parameterized on the linkage type: static or dynamic. -# -# $(1) = path to source file -# $(2) = linkage type: static | dynamic -# -source_to_object = $(patsubst $(source_directory)/%.c,$(build_objects_$(2)_directory)/%.o,$(1)) - -source_to_static_object = $(call source_to_object,$(1),static) -source_to_dynamic_object = $(call source_to_object,$(1),dynamic) - -source_to_objects = $(call source_to_static_object,$(1)) $(call source_to_dynamic_object,$(1)) - -# Converts a path to a process startup source code file to its corresponding start object file path. -# They may be written in assembly or C. -# -# $(1) = path to process startup source file -# -source_to_start_object = $(patsubst $(start_architecture_directory)/%,$(build_start_directory)/%.o,$(basename $(1))) - -# Converts the path to a source file to the path to its corresponding dependency data file -source_to_dependency = $(addprefix $(build_dependencies_directory)/,$(addsuffix .d,$(basename $(1)))) - -# Converts an example source file path to its corresponding executable file path. -# Parameterized on the linkage type: static or dynamic. -# -# $(1) = path to example source file -# $(2) = linkage type: static | dynamic -# -example_to_executable = $(patsubst $(examples_directory)/%.c,$(build_examples_$(2)_directory)/%,$(1)) - -example_to_static_executable = $(call example_to_executable,$(1),static) -example_to_dynamic_executable = $(call example_to_executable,$(1),dynamic) - -example_to_executables = $(call example_to_static_executable,$(1)) $(call example_to_dynamic_executable,$(1)) diff --git a/make/headers b/make/headers deleted file mode 100644 index 83f470e..0000000 --- a/make/headers +++ /dev/null @@ -1,3 +0,0 @@ -# List of liblinux headers -headers_library := $(wildcard $(include_liblinux_directory)/*.h) \ - $(wildcard $(include_liblinux_directory)/system_calls/*.h) diff --git a/make/objects b/make/objects deleted file mode 100644 index 108971a..0000000 --- a/make/objects +++ /dev/null @@ -1,14 +0,0 @@ -# List of liblinux objects that will be built for the static library -objects_static_library := $(call source_to_static_object,$(sources_library)) - -# List of liblinux objects that will be built for the dynamic library -objects_dynamic_library := $(call source_to_dynamic_object,$(sources_library)) - -# List of liblinux objects that will be built for all libraries -objects_libraries := $(objects_static_library) $(objects_dynamic_library) - -# List of liblinux objects that will be built and used as GCC startfiles -objects_start := $(call source_to_start_object,$(sources_start)) - -# List of all objects -objects := $(objects_libraries) $(objects_start) diff --git a/make/project b/make/project deleted file mode 100644 index f1c0b58..0000000 --- a/make/project +++ /dev/null @@ -1,4 +0,0 @@ -# Project information -library := linux -project := lib$(library) -architecture := x86_64 diff --git a/make/sources b/make/sources deleted file mode 100644 index 6dc6465..0000000 --- a/make/sources +++ /dev/null @@ -1,9 +0,0 @@ -# List of liblinux source code -sources_library := $(wildcard $(source_architecture_directory)/*.c) \ - $(wildcard $(source_directory)/system_calls/*.c) - -# List of liblinux process startup source code -sources_start := $(wildcard $(start_architecture_directory)/*.[csS]) - -# List of example programs that demonstrate how to use liblinux -sources_examples := $(wildcard $(examples_directory)/*.c) diff --git a/make/structure b/make/structure index 5fc23fa..6f47561 100644 --- a/make/structure +++ b/make/structure @@ -1,83 +1,13 @@ -# Project file system structure -source_directory := source include_directory := include -start_directory := start -examples_directory := examples -scripts_directory := scripts -build_directory := build - -source_architecture_directory := $(source_directory)/arch/$(architecture) include_liblinux_directory := $(include_directory)/liblinux -start_architecture_directory := $(start_directory)/$(architecture) -scripts_linux_directory := $(scripts_directory)/linux - -# Directories for build artifacts -build_root_directory := $(build_directory)/$(architecture) - -build_objects_directory := $(build_root_directory)/objects -build_objects_static_directory := $(build_objects_directory)/static -build_objects_dynamic_directory := $(build_objects_directory)/dynamic -build_libraries_directory := $(build_root_directory)/libraries -build_start_directory := $(build_libraries_directory)/$(start_directory) - -build_examples_directory := $(build_root_directory)/$(examples_directory) -build_examples_static_directory := $(build_examples_directory)/static -build_examples_dynamic_directory := $(build_examples_directory)/dynamic - -build_dependencies_directory := $(build_root_directory)/dependencies +source_directory := source +source_architecture_directory := $(source_directory)/arch/$(architecture) -build_scripts_directory := $(build_root_directory)/$(scripts_directory) +start_directory := start +start_architecture_directory := $(start_directory)/$(architecture) -# If the build directory is a symbolic link to another directory, -# ensure the existence of that directory. -build_directory_link := $(shell readlink $(build_directory)) -$(if $(build_directory_link),$(call execute,$(call ensure_directory_exists,$(build_directory_link)))) +examples_directory := examples -# build -# └── x86_64 -# ├── dependencies -# │   ├── examples -# │   │   ├── hello-world.d -# │   │   └── ... -# │   └── source -# │   ├── arch -# │   │   └── x86_64 -# │   │   └── system_call.d -# │   └── system_calls -# │   ├── exit.d -# │   └── ... -# ├── examples -# │   ├── dynamic -# │   │   ├── hello-world -# │   │   └── ... -# │   └── static -# │   ├── hello-world -# │   └── ... -# ├── libraries -# │   ├── liblinux.a -# │   ├── liblinux.so -# │   ├── liblinux.specs -# │   └── start -# │   ├── liblinux_start.o -# │   └── _start.o -# ├── objects -# │   ├── dynamic -# │   │   ├── arch -# │   │   │   └── x86_64 -# │   │   │   └── system_call.o -# │   │   └── system_calls -# │   │   ├── exit.o -# │   │   └── ... -# │   └── static -# │   ├── arch -# │   │   └── x86_64 -# │   │   └── system_call.o -# │   └── system_calls -# │   ├── exit.o -# │   └── ... -# └── scripts -# ├── liblinux-gcc -# ├── system-calls.available -# ├── system-calls.implemented -# └── system-calls.missing +scripts_directory := scripts +scripts_linux_directory := $(scripts_directory)/linux diff --git a/make/system_call_lists b/make/system_call_lists new file mode 100644 index 0000000..ef2e28a --- /dev/null +++ b/make/system_call_lists @@ -0,0 +1,41 @@ +# System calls available on the system + +system-calls.available.sh := $(scripts_directory)/system-calls.available.sh +system-calls.available := $(build_root_directory)/system-calls.available + +definitions.h := $(include_liblinux_directory)/definitions.h + +$(system-calls.available) : $(system-calls.available.sh) $(definitions.h) + $(call ensure_target_directory_exists) + $(call gcc.list_defined_macros,$(definitions.h)) | $< > $@ + +# System calls implemented in liblinux + +system-calls.implemented.sh := $(scripts_directory)/system-calls.implemented.sh +system-calls.implemented := $(build_root_directory)/system-calls.implemented + +liblinux_system_calls := $(basename $(notdir $(wildcard $(include_liblinux_directory)/system_calls/*.h))) + +$(system-calls.implemented) : $(system-calls.implemented.sh) + $(call ensure_target_directory_exists) + $< $(liblinux_system_calls) > $@ + +# System calls missing from liblinux + +system-calls.missing.sh := $(scripts_directory)/system-calls.missing.sh +system-calls.missing := $(build_root_directory)/system-calls.missing + +$(system-calls.missing) : $(system-calls.missing.sh) $(system-calls.available) $(system-calls.implemented) + $(call ensure_target_directory_exists) + $< $(system-calls.available) $(system-calls.implemented) > $@ + +# Phony targets + +define system_call_list_rule_template +.PHONY : system-calls.$(1) + +system-calls.$(1) : $$(system-calls.$(1)) + cat $$< +endef + +$(foreach list,available implemented missing,$(eval $(call system_call_list_rule_template,$(list)))) diff --git a/make/targets/all_phony b/make/targets/all_phony deleted file mode 100644 index 5ffa7d8..0000000 --- a/make/targets/all_phony +++ /dev/null @@ -1,32 +0,0 @@ -# Phony targets - -phony_targets += static-library -static-library: $(static_library) - -phony_targets += dynamic-library -dynamic-library: $(dynamic_library) - -phony_targets += libraries -libraries: static-library dynamic-library - -phony_targets += startfiles -startfiles: $(objects_start) - -phony_targets += static-examples -static-examples: $(examples_static_targets) - -phony_targets += dynamic-examples -dynamic-examples: $(examples_dynamic_targets) - -phony_targets += examples -examples: static-examples dynamic-examples - -phony_targets += all -all: libraries startfiles examples - -phony_targets += clean -clean: - rm -rf $(build_directory)/ - -include make/targets/phony/examples -include make/targets/phony/system_calls diff --git a/make/targets/dependencies b/make/targets/dependencies deleted file mode 100644 index 33cab9e..0000000 --- a/make/targets/dependencies +++ /dev/null @@ -1,46 +0,0 @@ -# Build rules for dependency files - -# Rule template for dependency data files. -# -# $(1) = path to source file -# $(2) = path to dependency file -# $(3) = make function that converts a path to a source file -# to a list of paths to all its targets -# -define dependency_file_rule_template -dependency_files += $(2) - -$(2) : $(1) - $$(call ensure_target_directory_exists) - $$(call compiler.generate_dependency_data,$$@,$$<,$$(call $(3),$$<)) -endef - -# Computes the parameters for and evaluates the dependency file rule template. -# -# $(1) = path to source source file -# $(2) = make function that converts a path to a source file -# to a list of paths to all its targets -# -generate_dependency_file_rule = $(call dependency_file_rule_template,$(1),$(call source_to_dependency,$(1)),$(2)) - -# Generate dependency file rules for library sources, process startup code and examples. -$(foreach file,$(sources_library),$(eval $(call generate_dependency_file_rule,$(file),source_to_objects))) -$(foreach file,$(sources_start),$(eval $(call generate_dependency_file_rule,$(file),source_to_start_object))) -$(foreach file,$(sources_examples),$(eval $(call generate_dependency_file_rule,$(file),example_to_executables))) - -targets += $(dependency_files) - -# The wildcard function returns existing files that match its parameters. -# This prevents make from including files that don't exist. -# -# Make tries to rebuild included files, without the wildcard function -# the dependency data files would be generated with every invocation of make, -# even make clean. -# -# References: -# -# https://www.gnu.org/software/make/manual/html_node/Remaking-Makefiles.html -# https://make.mad-scientist.net/papers/advanced-auto-dependency-generation/#include -# https://make.mad-scientist.net/constructed-include-files/ -# -include $(wildcard $(dependency_files)) diff --git a/make/targets/dynamic_library b/make/targets/dynamic_library deleted file mode 100644 index 79d9c3c..0000000 --- a/make/targets/dynamic_library +++ /dev/null @@ -1,7 +0,0 @@ -dynamic_library := $(build_libraries_directory)/$(project).so - -$(dynamic_library) : $(objects_dynamic_library) - $(call ensure_target_directory_exists) - $(call compiler.compile_dynamic_library,$@,$^) - -targets += $(dynamic_library) diff --git a/make/targets/examples b/make/targets/examples deleted file mode 100644 index 464f6ea..0000000 --- a/make/targets/examples +++ /dev/null @@ -1,51 +0,0 @@ -# Rule template for example executables. -# -# The reason why the executables depend on their dependency data file -# is to trigger the generation of dependency data when it is updated. -# Once generated, future executions of make will benefit from the dependency data. -# Dependency data file generation rules are defined in make/targets/dependencies. -# -# $(1) = path to source file -# $(2) = path to executable file -# $(3) = path to dependency data file -# $(4) = linkage type: static | dynamic -# -# References: -# -# https://make.mad-scientist.net/papers/advanced-auto-dependency-generation/#depdelete -# -define example_rule_template -examples_$(4)_targets += $(2) - -$(2) : $(1) $(3) $$($(4)_library) $$(objects_start) $$(gcc_wrapper) - $$(call ensure_target_directory_exists) - $$(call gcc.compile_example,$$@,$$<,$(4)) -endef - -# Computes the parameters for and evaluates the example rule template. -# -# $(1) = path to example source file -# $(2) = linkage type: static | dynamic -# -generate_example_rule = $(call example_rule_template,$(1),$(call example_to_executable,$(1),$(2)),$(call source_to_dependency,$(1)),$(2)) - -# Rule generators for statically and dynamically linked example executables, -# either individually or both a the same time. -# -# $(1) = path to example source file -# -generate_static_example_rule = $(call generate_example_rule,$(1),static) -generate_dynamic_example_rule = $(call generate_example_rule,$(1),dynamic) - -define generate_example_rules -$(call generate_static_example_rule,$(1)) - -$(call generate_dynamic_example_rule,$(1)) -endef - -# Generate example executable rules for every example source file -$(foreach example,$(sources_examples),$(eval $(call generate_example_rules,$(example)))) - -# Compute list of all example targets -examples_targets += $(examples_static_targets) $(examples_dynamic_targets) -targets += $(examples_targets) diff --git a/make/targets/gcc_wrapper b/make/targets/gcc_wrapper deleted file mode 100644 index c51b824..0000000 --- a/make/targets/gcc_wrapper +++ /dev/null @@ -1,16 +0,0 @@ -# GCC wrapper script and the specs file used by it -# Makes building example and application code painless - -gcc_wrapper := $(build_scripts_directory)/$(notdir $(basename $(gcc_wrapper_script))) -gcc_specs := $(build_libraries_directory)/$(notdir $(basename $(gcc_specs_script))) - -$(gcc_wrapper) : $(gcc_specs) $(gcc_wrapper_script) - $(call ensure_target_directory_exists) - $(gcc_wrapper_script) $< > $@ - chmod +x $@ - -$(gcc_specs) : $(gcc_specs_script) - $(call ensure_target_directory_exists) - $(gcc_specs_script) $(build_libraries_directory) $(objects_start) > $@ - -targets += $(gcc_wrapper) $(gcc_specs) diff --git a/make/targets/objects b/make/targets/objects deleted file mode 100644 index 147954c..0000000 --- a/make/targets/objects +++ /dev/null @@ -1,47 +0,0 @@ -# Automatic build rule generation for object files - -# Rule template for object files. -# -# The reason why objects depend on their dependency data file -# is to trigger the generation of dependency data when it is updated. -# Once generated, future executions of make will benefit from the dependency data. -# Dependency data file generation rules are defined in make/targets/dependencies. -# -# $(1) = path to source file -# $(2) = path to object file -# $(3) = path to dependency data file -# $(4) = linkage type -# -# References: -# -# https://make.mad-scientist.net/papers/advanced-auto-dependency-generation/#depdelete -# -define object_rule_template -$(2) : $(1) $(3) - $$(call ensure_target_directory_exists) - $$(call compiler.compile_object,$$@,$$<,$(4)) -endef - -# Computes the parameters for and evaluates the object rule template. -# -# $(1) = path to source file -# $(2) = linkage type -# -generate_object_rule = $(call object_rule_template,$(1),$(call source_to_object,$(1),$(2)),$(call source_to_dependency,$(1)),$(2)) - -# Rule generators for statically and dynamically linked objects, -# either individually or both a the same time. -# -# $(1) = path to source file -# -generate_static_object_rule = $(call generate_object_rule,$(1),static) -generate_dynamic_object_rule = $(call generate_object_rule,$(1),dynamic) - -define generate_object_rules -$(call generate_static_object_rule,$(1)) - -$(call generate_dynamic_object_rule,$(1)) -endef - -# Generate object file rules for every source file -$(foreach source,$(sources_library),$(eval $(call generate_object_rules,$(source)))) diff --git a/make/targets/phony/examples b/make/targets/phony/examples deleted file mode 100644 index 1a82b3d..0000000 --- a/make/targets/phony/examples +++ /dev/null @@ -1,15 +0,0 @@ -define generate_run_example_rule -phony_targets += run-$(1) run-static-$(1) run-dynamic-$(1) - -run-$(1) : run-static-$(1) - -run-static-$(1) : $$(build_examples_static_directory)/$(1) - $$(build_examples_static_directory)/$(1) - -run-dynamic-$(1) : $$(build_examples_dynamic_directory)/$(1) - LD_LIBRARY_PATH=$$(build_libraries_directory) $$(build_examples_dynamic_directory)/$(1) -endef - -$(foreach example,$(examples),$(eval $(call generate_run_example_rule,$(example)))) - -undefine generate_run_example_rule diff --git a/make/targets/phony/system_calls b/make/targets/phony/system_calls deleted file mode 100644 index d02dfcd..0000000 --- a/make/targets/phony/system_calls +++ /dev/null @@ -1,11 +0,0 @@ -system_call_lists := $(notdir $(wildcard make/targets/system_calls/*)) - -define generate_system_call_list_rule -phony_targets += system-calls.$(1) -system-calls.$(1) : $$(system-calls.$(1)) - cat $$< -endef - -$(foreach list,$(system_call_lists),$(eval $(call generate_system_call_list_rule,$(list)))) - -undefine generate_system_call_list_rule diff --git a/make/targets/start b/make/targets/start deleted file mode 100644 index c490486..0000000 --- a/make/targets/start +++ /dev/null @@ -1,24 +0,0 @@ -# Build targets for the liblinux process startup objects: -# -# _start.o -# liblinux_start.o - -# Rule template for process start objects. -# -# $(1) = path to start code file -# $(2) = path to start object file -# -define start_object_rule_template -$(2) : $(1) $(3) - $$(call ensure_target_directory_exists) - $$(call compiler.compile_startup_object,$$@,$$<) -endef - -# Computes the parameters for and evaluates the start object rule template. -# -# $(1) = path to start code file -# -generate_start_object_rule = $(call start_object_rule_template,$(1),$(call source_to_start_object,$(1)),$(call source_to_dependency,$(1))) - -# Generate start object rules for all start code files -$(foreach file,$(sources_start),$(eval $(call generate_start_object_rule,$(file)))) diff --git a/make/targets/static_library b/make/targets/static_library deleted file mode 100644 index 1185125..0000000 --- a/make/targets/static_library +++ /dev/null @@ -1,7 +0,0 @@ -static_library := $(build_libraries_directory)/$(project).a - -$(static_library) : $(objects_static_library) - $(call ensure_target_directory_exists) - $(call archiver.create,$@,$^) - -targets += $(static_library) diff --git a/make/targets/system_calls/available b/make/targets/system_calls/available deleted file mode 100644 index c5867ec..0000000 --- a/make/targets/system_calls/available +++ /dev/null @@ -1,8 +0,0 @@ -system-calls.available.sh := $(scripts_directory)/system-calls.available.sh -system-calls.available := $(build_scripts_directory)/system-calls.available - -definitions.h := $(include_liblinux_directory)/definitions.h - -$(system-calls.available) : $(system-calls.available.sh) $(definitions.h) - $(call ensure_target_directory_exists) - $(call gcc.list_defined_macros,$(definitions.h)) | $< > $@ diff --git a/make/targets/system_calls/implemented b/make/targets/system_calls/implemented deleted file mode 100644 index b409de3..0000000 --- a/make/targets/system_calls/implemented +++ /dev/null @@ -1,8 +0,0 @@ -system-calls.implemented.sh := $(scripts_directory)/system-calls.implemented.sh -system-calls.implemented := $(build_scripts_directory)/system-calls.implemented - -liblinux_system_calls := $(basename $(notdir $(wildcard $(include_liblinux_directory)/system_calls/*.h))) - -$(system-calls.implemented) : $(system-calls.implemented.sh) - $(call ensure_target_directory_exists) - $< $(liblinux_system_calls) > $@ diff --git a/make/targets/system_calls/missing b/make/targets/system_calls/missing deleted file mode 100644 index 5abb604..0000000 --- a/make/targets/system_calls/missing +++ /dev/null @@ -1,6 +0,0 @@ -system-calls.missing.sh := $(scripts_directory)/system-calls.missing.sh -system-calls.missing := $(build_scripts_directory)/system-calls.missing - -$(system-calls.missing) : $(system-calls.missing.sh) $(system-calls.available) $(system-calls.implemented) - $(call ensure_target_directory_exists) - $< $(system-calls.available) $(system-calls.implemented) > $@