nitc :: MakefileToolchain :: defaultinit
# Default toolchain using a Makefile
class MakefileToolchain
super Toolchain
redef fun write_and_make
do
var debug = toolcontext.opt_debug.value
var compile_dir = compile_dir
# Remove the compilation directory unless explicitly set
var auto_remove = toolcontext.opt_compile_dir.value == null
# If debug flag is set, do not remove sources
if debug then auto_remove = false
# Generate the .h and .c files
# A single C file regroups many compiled rumtime functions
# Note that we do not try to be clever an a small change in a Nit source file may change the content of all the generated .c files
var time0 = get_time
self.toolcontext.info("*** WRITING C ***", 1)
root_compile_dir.mkdir
compile_dir.mkdir
var cfiles = new Array[String]
write_files(compile_dir, cfiles)
# Generate the Makefile
write_makefile(compile_dir, cfiles)
var time1 = get_time
self.toolcontext.info("*** END WRITING C: {time1-time0} ***", 2)
toolcontext.check_errors
# Execute the Makefile
if self.toolcontext.opt_no_cc.value then return
time0 = time1
self.toolcontext.info("*** COMPILING C ***", 1)
compile_c_code(compile_dir)
if auto_remove then
sys.system("rm -r -- '{root_compile_dir.escape_to_sh}/'")
end
if toolcontext.opt_run.value then
var mainmodule = compiler.mainmodule
var out = outfile(mainmodule)
var cmd = ["."/out]
cmd.append toolcontext.option_context.rest
toolcontext.exec_and_check(cmd, "--run")
end
time1 = get_time
self.toolcontext.info("*** END COMPILING C: {time1-time0} ***", 2)
end
# Write all source files to the `compile_dir`
fun write_files(compile_dir: String, cfiles: Array[String])
do
var platform = compiler.target_platform
if platform.supports_libunwind then compiler.build_c_to_nit_bindings
var cc_opt_with_libgc = "-DWITH_LIBGC"
if not platform.supports_libgc then cc_opt_with_libgc = ""
# Add gc_choser.h to aditionnal bodies
var gc_chooser = new ExternCFile("gc_chooser.c", cc_opt_with_libgc)
if cc_opt_with_libgc != "" then gc_chooser.pkgconfigs.add "bdw-gc"
compiler.extern_bodies.add(gc_chooser)
var clib = toolcontext.nit_dir / "clib"
compiler.files_to_copy.add "{clib}/gc_chooser.c"
compiler.files_to_copy.add "{clib}/gc_chooser.h"
# Add lttng traces provider to external bodies
if toolcontext.opt_trace.value then
#-I. is there in order to make the TRACEPOINT_INCLUDE directive in clib/traces.h refer to the directory in which gcc was invoked.
var traces = new ExternCFile("traces.c", "-I.")
traces.pkgconfigs.add "lttng-ust"
compiler.extern_bodies.add(traces)
compiler.files_to_copy.add "{clib}/traces.c"
compiler.files_to_copy.add "{clib}/traces.h"
end
# FFI
for m in compiler.mainmodule.in_importation.greaters do
compiler.finalize_ffi_for_module(m)
end
# Copy original .[ch] files to compile_dir
for src in compiler.files_to_copy do
var basename = src.basename
var dst = "{compile_dir}/{basename}"
src.file_copy_to dst
end
var hfilename = compiler.header.file.name + ".h"
var hfilepath = "{compile_dir}/{hfilename}"
var h = new FileWriter.open(hfilepath)
for l in compiler.header.decl_lines do
h.write l
h.write "\n"
end
for l in compiler.header.lines do
h.write l
h.write "\n"
end
h.close
var max_c_lines = toolcontext.opt_max_c_lines.value
for f in compiler.files do
var i = 0
var count = 0
var file: nullable FileWriter = null
for vis in f.writers do
if vis == compiler.header then continue
var total_lines = vis.lines.length + vis.decl_lines.length
if total_lines == 0 then continue
count += total_lines
if file == null or (count > max_c_lines and max_c_lines > 0) then
i += 1
if file != null then file.close
var cfilename = "{f.name}.{i}.c"
var cfilepath = "{compile_dir}/{cfilename}"
self.toolcontext.info("new C source files to compile: {cfilepath}", 3)
cfiles.add(cfilename)
file = new FileWriter.open(cfilepath)
file.write "#include \"{f.name}.0.h\"\n"
count = total_lines
end
for l in vis.decl_lines do
file.write l
file.write "\n"
end
for l in vis.lines do
file.write l
file.write "\n"
end
end
if file == null then continue
file.close
var cfilename = "{f.name}.0.h"
var cfilepath = "{compile_dir}/{cfilename}"
var hfile: nullable FileWriter = null
hfile = new FileWriter.open(cfilepath)
hfile.write "#include \"{hfilename}\"\n"
for key in f.required_declarations do
if not compiler.provided_declarations.has_key(key) then
var node = compiler.requirers_of_declarations.get_or_null(key)
if node != null then
node.debug "No provided declaration for {key}"
else
print "No provided declaration for {key}"
end
abort
end
hfile.write compiler.provided_declarations[key]
hfile.write "\n"
end
hfile.close
end
self.toolcontext.info("Total C source files to compile: {cfiles.length}", 2)
end
# Get the name of the Makefile to use
fun makefile_name: String do return "{compiler.mainmodule.c_name}.mk"
# Get the default name of the executable to produce
fun default_outname: String
do
var mainmodule = compiler.mainmodule.first_real_mmodule
return mainmodule.name
end
# Combine options and platform informations to get the final path of the outfile
fun outfile(mainmodule: MModule): String
do
var res = self.toolcontext.opt_output.value
if res != null then return res
res = default_outname
var dir = self.toolcontext.opt_dir.value
if dir != null then return dir.join_path(res)
return res
end
# Write the Makefile
fun write_makefile(compile_dir: String, cfiles: Array[String])
do
var mainmodule = compiler.mainmodule
var platform = compiler.target_platform
var outname = outfile(mainmodule)
var real_outpath = compile_dir.relpath(outname)
var outpath = real_outpath.escape_to_mk
if outpath != real_outpath then
# If the name is crazy and need escaping, we will do an indirection
# 1. generate the binary in the nit_compile dir under an escaped name
# 2. copy the binary at the right place in the `all` goal.
outpath = mainmodule.c_name
end
var makename = makefile_name
var makepath = "{compile_dir}/{makename}"
var makefile = new FileWriter.open(makepath)
var linker_options = new HashSet[String]
for m in mainmodule.in_importation.greaters do
var libs = m.collect_linker_libs
if libs != null then linker_options.add_all(libs)
end
var debug = toolcontext.opt_debug.value
makefile.write """
ifeq ($(origin CC), default)
CC = ccache cc
endif
ifeq ($(origin CXX), default)
CXX = ccache c++
endif
CFLAGS ?= -g {{{if not debug then "-O2" else ""}}} -Wno-unused-value -Wno-switch -Wno-attributes -Wno-trigraphs
CINCL =
LDFLAGS ?=
LDLIBS ?= -lm {{{linker_options.join(" ")}}}
\n"""
if self.toolcontext.opt_trace.value then makefile.write "LDLIBS += -llttng-ust -ldl\n"
if toolcontext.opt_shared_lib.value then
makefile.write """
CFLAGS += -fPIC
LDFLAGS += -shared -Wl,-soname,{{{outname}}}
"""
end
makefile.write "\n# SPECIAL CONFIGURATION FLAGS\n"
if platform.supports_libunwind then
if toolcontext.opt_no_stacktrace.value then
makefile.write "NO_STACKTRACE ?= True"
else
makefile.write "NO_STACKTRACE ?= # Set to `True` to enable"
end
end
# Dynamic adaptations
# While `platform` enable complex toolchains, they are statically applied
# For a dynamic adaptsation of the compilation, the generated Makefile should check and adapt things itself
makefile.write "\n\n"
# Check and adapt the targeted system
makefile.write("uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')\n")
# Check and adapt for the compiler used
# clang need an additionnal `-Qunused-arguments`
makefile.write("clang_check := $(shell sh -c '$(CC) -v 2>&1 | grep -q clang; echo $$?')\nifeq ($(clang_check), 0)\n\tCFLAGS += -Qunused-arguments\nendif\n")
if platform.supports_libunwind then
makefile.write """
ifneq ($(NO_STACKTRACE), True)
# Check and include lib-unwind in a portable way
ifneq ($(uname_S),Darwin)
# already included on macosx, but need to get the correct flags in other supported platforms.
ifeq ($(shell pkg-config --exists 'libunwind'; echo $$?), 0)
LDLIBS += `pkg-config --libs libunwind`
CFLAGS += `pkg-config --cflags libunwind`
else
$(warning "[_] stack-traces disabled. Please install libunwind-dev.")
CFLAGS += -D NO_STACKTRACE
endif
endif
else
# Stacktraces disabled
CFLAGS += -D NO_STACKTRACE
endif
"""
else
makefile.write("CFLAGS += -D NO_STACKTRACE\n\n")
end
makefile.write """
# Special configuration for Darwin
ifeq ($(uname_S),Darwin)
# Remove POSIX flag -lrt
LDLIBS := $(filter-out -lrt,$(LDLIBS))
endif
# Special configuration for Windows under mingw64
ifneq ($(findstring MINGW64,$(uname_S)),)
# Use the pcreposix regex library
LDLIBS += -lpcreposix
# Remove POSIX flag -lrt
LDLIBS := $(filter-out -lrt,$(LDLIBS))
# Silence warnings when storing Int, Char and Bool as pointer address
CFLAGS += -Wno-pointer-to-int-cast -Wno-int-to-pointer-cast
endif
# Add the compilation dir to the Java CLASSPATH
ifeq ($(CLASSPATH),)
CLASSPATH := .
else
CLASSPATH := $(CLASSPATH):.
endif
"""
makefile.write("all: {outpath}\n")
if outpath != real_outpath then
makefile.write("\tcp -- {outpath.escape_to_sh} {real_outpath.escape_to_sh.replace("$","$$")}")
end
makefile.write("\n")
var ofiles = new Array[String]
var dep_rules = new Array[String]
# Compile each generated file
for f in cfiles do
var o = f.strip_extension(".c") + ".o"
makefile.write("{o}: {f}\n\t$(CC) $(CFLAGS) $(CINCL) -c -o {o} {f}\n\n")
ofiles.add(o)
dep_rules.add(o)
end
# Generate linker script, if any
if not compiler.linker_script.is_empty then
var linker_script_path = "{compile_dir}/linker_script"
ofiles.add "linker_script"
var f = new FileWriter.open(linker_script_path)
for l in compiler.linker_script do
f.write l
f.write "\n"
end
f.close
end
# pkg-config annotation support
var pkgconfigs = new Array[String]
for f in compiler.extern_bodies do
pkgconfigs.add_all f.pkgconfigs
end
# Only test if pkg-config is used
if not pkgconfigs.is_empty then
# Check availability of pkg-config, silence the proc output
toolcontext.check_pkgconfig_packages pkgconfigs
# Double the check in the Makefile in case it's distributed
makefile.write """
# does pkg-config exists?
ifneq ($(shell which pkg-config >/dev/null; echo $$?), 0)
$(error "Command `pkg-config` not found. Please install it")
endif
"""
for p in pkgconfigs do
makefile.write """
# Check for library {{{p}}}
ifneq ($(shell pkg-config --exists '{{{p}}}'; echo $$?), 0)
$(error "pkg-config: package {{{p}}} is not found.")
endif
"""
end
end
# Compile each required extern body into a specific .o
var java_files = new Array[ExternFile]
for f in compiler.extern_bodies do
var o = f.makefile_rule_name
makefile.write("{o}: {f.filename}\n")
makefile.write("\t{f.makefile_rule_content}\n\n")
dep_rules.add(f.makefile_rule_name)
if f.compiles_to_o_file then ofiles.add(o)
if f.add_to_jar then java_files.add(f)
end
if not java_files.is_empty then
var jar_file = "{outpath}.jar"
var class_files_array = new Array[String]
for f in java_files do class_files_array.add(f.makefile_rule_name)
var class_files = class_files_array.join(" ")
makefile.write("{jar_file}: {class_files}\n")
makefile.write("\tjar cf {jar_file} {class_files}\n\n")
dep_rules.add jar_file
end
# Link edition
var pkg = ""
if not pkgconfigs.is_empty then
pkg = "`pkg-config --libs {pkgconfigs.join(" ")}`"
end
makefile.write("{outpath}: {dep_rules.join(" ")}\n\t$(CC) $(LDFLAGS) -o {outpath.escape_to_sh} {ofiles.join(" ")} $(LDLIBS) {pkg}\n\n")
# Clean
makefile.write("clean:\n\trm -f {ofiles.join(" ")} 2>/dev/null\n")
if outpath != real_outpath then
makefile.write("\trm -f -- {outpath.escape_to_sh} 2>/dev/null\n")
end
makefile.close
self.toolcontext.info("Generated makefile: {makepath}", 2)
makepath.file_copy_to "{compile_dir}/Makefile"
end
# The C code is generated, compile it to an executable
fun compile_c_code(compile_dir: String)
do
var makename = makefile_name
var makeflags = self.toolcontext.opt_make_flags.value
if makeflags == null then makeflags = ""
var command = "make -B -C {compile_dir} -f {makename} -j 4 {makeflags}"
self.toolcontext.info(command, 2)
var res
if self.toolcontext.verbose_level >= 3 then
res = sys.system("{command} 2>&1")
else if is_windows then
res = sys.system("{command} 2>&1 >nul")
else
res = sys.system("{command} 2>&1 >/dev/null")
end
if res != 0 then
toolcontext.error(null, "Compilation Error: `make` failed with error code: {res}. The command was `{command}`.")
end
end
end
src/compiler/abstract_compiler.nit:166,1--598,3