var opt_no_cc = new OptionBool("Do not invoke C compiler", "--no-cc")
# --no-main
var opt_no_main = new OptionBool("Do not generate main entry point", "--no-main")
- # --cc-paths
- var opt_cc_path = new OptionArray("Set include path for C header files (may be used more than once)", "--cc-path")
# --make-flags
var opt_make_flags = new OptionString("Additional options to make", "--make-flags")
+ # --max-c-lines
+ var opt_max_c_lines = new OptionInt("Maximum number of lines in generated C files. Use 0 for unlimited", 10000, "--max-c-lines")
+ # --group-c-files
+ var opt_group_c_files = new OptionBool("Group all generated code in the same series of files", "--group-c-files")
# --compile-dir
var opt_compile_dir = new OptionString("Directory used to generate temporary files", "--compile-dir")
# --hardening
self.option_context.add_option(self.opt_stacktrace)
self.option_context.add_option(self.opt_no_gcc_directive)
self.option_context.add_option(self.opt_release)
+ self.option_context.add_option(self.opt_max_c_lines, self.opt_group_c_files)
+
+ opt_no_main.hidden = true
end
redef fun process_options(args)
class MakefileToolchain
super Toolchain
- # The list of directories to search for included C headers (-I for C compilers)
- # The list is initially set with :
- # * the toolcontext --cc-path option
- # * the NIT_CC_PATH environment variable
- # * `toolcontext.nit_dir`
- # Path can be added (or removed) by the client
- var cc_paths = new Array[String]
-
- protected fun gather_cc_paths
- do
- # Look for the the Nit clib path
- var path_env = toolcontext.nit_dir
- if path_env != null then
- var libname = "{path_env}/clib"
- if libname.file_exists then cc_paths.add(libname)
- end
-
- if cc_paths.is_empty then
- toolcontext.error(null, "Cannot determine the nit clib path. define envvar NIT_DIR.")
- end
-
- # Add user defined cc_paths
- cc_paths.append(toolcontext.opt_cc_path.value)
-
- path_env = "NIT_CC_PATH".environ
- if not path_env.is_empty then
- cc_paths.append(path_env.split_with(':'))
- end
- end
redef fun write_and_make(compiler)
do
- gather_cc_paths
-
var compile_dir = compile_dir
# Generate the .h and .c files
# 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)
- compiler.files_to_copy.add "{cc_paths.first}/gc_chooser.c"
- compiler.files_to_copy.add "{cc_paths.first}/gc_chooser.h"
+ var clib = toolcontext.nit_dir / "clib"
+ compiler.files_to_copy.add "{clib}/gc_chooser.c"
+ compiler.files_to_copy.add "{clib}/gc_chooser.h"
# FFI
for m in compiler.mainmodule.in_importation.greaters do
end
h.close
+ var max_c_lines = toolcontext.opt_max_c_lines.value
for f in compiler.files do
var i = 0
- var hfile: nullable OFStream = null
var count = 0
- var cfilename = "{f.name}.0.h"
- var cfilepath = "{compile_dir}/{cfilename}"
- hfile = new OFStream.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
var file: nullable OFStream = 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 > 10000 then
+ if file == null or (count > max_c_lines and max_c_lines > 0) then
i += 1
if file != null then file.close
- cfilename = "{f.name}.{i}.c"
- cfilepath = "{compile_dir}/{cfilename}"
+ 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 OFStream.open(cfilepath)
file.write "\n"
end
end
- if file != null then file.close
+ if file == null then continue
+ file.close
+
+ var cfilename = "{f.name}.0.h"
+ var cfilepath = "{compile_dir}/{cfilename}"
+ var hfile: nullable OFStream = null
+ hfile = new OFStream.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
- fun makefile_name(mainmodule: MModule): String do return "{mainmodule.name}.mk"
+ fun makefile_name(mainmodule: MModule): String do return "{mainmodule.c_name}.mk"
fun default_outname(mainmodule: MModule): String
do
var outname = outfile(mainmodule)
- var orig_dir = compile_dir.relpath(".")
- var outpath = orig_dir.join_path(outname).simplify_path
+ 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(mainmodule)
var makepath = "{compile_dir}/{makename}"
var makefile = new OFStream.open(makepath)
- var cc_includes = ""
- for p in cc_paths do
- cc_includes += " -I \"" + p + "\""
- end
-
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
- makefile.write("CC = ccache cc\nCXX = ccache c++\nCFLAGS = -g -O2 -Wno-unused-value -Wno-switch\nCINCL = {cc_includes}\nLDFLAGS ?= \nLDLIBS ?= -lm -lgc {linker_options.join(" ")}\n\n")
+ makefile.write("CC = ccache cc\nCXX = ccache c++\nCFLAGS = -g -O2 -Wno-unused-value -Wno-switch\nCINCL =\nLDFLAGS ?= \nLDLIBS ?= -lm {linker_options.join(" ")}\n\n")
var ost = toolcontext.opt_stacktrace.value
if (ost == "libunwind" or ost == "nitstack") and (platform == null or platform.supports_libunwind) then makefile.write("NEED_LIBUNWIND := YesPlease\n")
makefile.write("ifdef NEED_LIBUNWIND\n\tLDLIBS += -lunwind\nendif\n")
- makefile.write("all: {outpath}\n\n")
+ 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]
var java_files = new Array[ExternFile]
+ var pkgconfigs = new Array[String]
+ for f in compiler.extern_bodies do
+ pkgconfigs.add_all f.pkgconfigs
+ end
+ # Protect pkg-config
+ if not pkgconfigs.is_empty then
+ 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
for f in compiler.extern_bodies do
var o = f.makefile_rule_name
end
# Link edition
- makefile.write("{outpath}: {dep_rules.join(" ")}\n\t$(CC) $(LDFLAGS) -o {outpath} {ofiles.join(" ")} $(LDLIBS)\n\n")
+ 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 {ofiles.join(" ")} 2>/dev/null\n\n")
+ makefile.write("clean:\n\trm {ofiles.join(" ")} 2>/dev/null\n")
+ if outpath != real_outpath then
+ makefile.write("\trm -- {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
fun compile_c_code(compiler: AbstractCompiler, compile_dir: String)
var mainmodule: MModule is writable
# The real main module of the program
- var realmainmodule: MModule
+ var realmainmodule: MModule is noinit
# The modelbuilder used to know the model and the AST
var modelbuilder: ModelBuilder is protected writable
# Is hardening asked? (see --hardening)
fun hardening: Bool do return self.modelbuilder.toolcontext.opt_hardening.value
- init(mainmodule: MModule, modelbuilder: ModelBuilder)
+ init
do
- self.mainmodule = mainmodule
self.realmainmodule = mainmodule
- self.modelbuilder = modelbuilder
end
# Force the creation of a new file
# The point is to avoid contamination between must-be-compiled-separately files
fun new_file(name: String): CodeFile
do
+ if modelbuilder.toolcontext.opt_group_c_files.value then
+ if self.files.is_empty then
+ var f = new CodeFile(mainmodule.c_name)
+ self.files.add(f)
+ end
+ return self.files.first
+ end
var f = new CodeFile(name)
self.files.add(f)
return f
fun new_visitor: VISITOR is abstract
# Where global declaration are stored (the main .h)
- var header: CodeWriter is writable
+ var header: CodeWriter is writable, noinit
# Provide a declaration that can be requested (before or latter) by a visitor
fun provide_declaration(key: String, s: String)
stream.write("static const C_Nit_Names map[{names.length}] = \{\n")
for i in names.keys do
stream.write("\{\"")
- stream.write(i)
+ stream.write(i.escape_to_c)
stream.write("\",\"")
- stream.write(names[i])
+ stream.write(names[i].escape_to_c)
stream.write("\"\},\n")
end
stream.write("\};\n")
var cds = mtype.collect_mclassdefs(self.mainmodule).to_a
self.mainmodule.linearize_mclassdefs(cds)
for cd in cds do
- if not self.modelbuilder.mclassdef2nclassdef.has_key(cd) then continue
- var n = self.modelbuilder.mclassdef2nclassdef[cd]
- for npropdef in n.n_propdefs do
- if npropdef isa AAttrPropdef then
- npropdef.init_expr(v, recv)
- end
+ for npropdef in modelbuilder.collect_attr_propdef(cd) do
+ npropdef.init_expr(v, recv)
end
end
end
var cds = mtype.collect_mclassdefs(self.mainmodule).to_a
self.mainmodule.linearize_mclassdefs(cds)
for cd in cds do
- if not self.modelbuilder.mclassdef2nclassdef.has_key(cd) then continue
- var n = self.modelbuilder.mclassdef2nclassdef[cd]
- for npropdef in n.n_propdefs do
- if npropdef isa AAttrPropdef then
- npropdef.check_expr(v, recv)
- end
+ for npropdef in modelbuilder.collect_attr_propdef(cd) do
+ npropdef.check_expr(v, recv)
end
end
end
# (used for local or global declaration)
fun add_decl(s: String) do self.decl_lines.add(s)
- init(file: CodeFile)
+ init
do
- self.file = file
file.writers.add(self)
end
end
var current_node: nullable ANode = null is writable
# The current `Frame`
- var frame: nullable Frame is writable
+ var frame: nullable Frame = null is writable
# Alias for self.compiler.mainmodule.object_type
fun object_type: MClassType do return self.compiler.mainmodule.object_type
# Alias for self.compiler.mainmodule.bool_type
fun bool_type: MClassType do return self.compiler.mainmodule.bool_type
- var writer: CodeWriter
+ var writer: CodeWriter is noinit
- init(compiler: COMPILER)
+ init
do
- self.compiler = compiler
self.writer = new CodeWriter(compiler.files.last)
end
# false (usual value) means that the variable is a mcasttype or a subtype.
var is_exact: Bool = false is writable
- init(name: String, mtype: MType, mcasttype: MType)
+ init
do
- self.name = name
- self.mtype = mtype
- self.mcasttype = mcasttype
assert not mtype.need_anchor
assert not mcasttype.need_anchor
end
do
var res = self.c_name_cache
if res != null then return res
- res = "{mclass.intro_mmodule.name.to_cmangle}__{mclass.name.to_cmangle}"
+ res = "{mclass.intro_mmodule.c_name}__{mclass.name.to_cmangle}"
self.c_name_cache = res
return res
end
fun c_name: String do
var res = self.c_name_cache
if res != null then return res
- res = "{intro_mmodule.name.to_cmangle}__{name.to_cmangle}"
+ res = "{intro_mmodule.c_name}__{name.to_cmangle}"
self.c_name_cache = res
return res
end
do
var res = self.c_name_cache
if res != null then return res
- res = "{self.mclassdef.mmodule.name.to_cmangle}__{self.mclassdef.mclass.name.to_cmangle}__{self.mproperty.name.to_cmangle}"
+ res = "{self.mclassdef.mmodule.c_name}__{self.mclassdef.mclass.name.to_cmangle}__{self.mproperty.name.to_cmangle}"
self.c_name_cache = res
return res
end
do
if is_abstract then return true
var modelbuilder = v.compiler.modelbuilder
- if modelbuilder.mpropdef2npropdef.has_key(self) then
- var npropdef = modelbuilder.mpropdef2npropdef[self]
- return npropdef.can_inline
- else if self.mproperty.is_root_init then
+ var node = modelbuilder.mpropdef2node(self)
+ if node isa APropdef then
+ return node.can_inline
+ else if node isa AClassdef then
# Automatic free init is always inlined since it is empty or contains only attribtes assigments
return true
else
do
var modelbuilder = v.compiler.modelbuilder
var val = constant_value
- if modelbuilder.mpropdef2npropdef.has_key(self) then
- var npropdef = modelbuilder.mpropdef2npropdef[self]
+ var node = modelbuilder.mpropdef2node(self)
+ if node isa APropdef then
var oldnode = v.current_node
- v.current_node = npropdef
+ v.current_node = node
self.compile_parameter_check(v, arguments)
- npropdef.compile_to_c(v, self, arguments)
+ node.compile_to_c(v, self, arguments)
v.current_node = oldnode
- else if self.mproperty.is_root_init then
- var nclassdef = modelbuilder.mclassdef2nclassdef[self.mclassdef]
+ else if node isa AClassdef then
var oldnode = v.current_node
- v.current_node = nclassdef
+ v.current_node = node
self.compile_parameter_check(v, arguments)
- nclassdef.compile_to_c(v, self, arguments)
+ node.compile_to_c(v, self, arguments)
v.current_node = oldnode
else if val != null then
v.ret(v.value_instance(val))
end
redef class MModule
+ # Return the name of the global C identifier associated to `self`.
+ # This name is used to prefix files and other C identifiers associated with `self`.
+ var c_name: String is lazy do
+ var g = mgroup
+ var res
+ if g != null and g.mproject.name != name then
+ res = g.mproject.name.to_cmangle + "__" + name.to_cmangle
+ else
+ res = name.to_cmangle
+ end
+ return res
+ end
+
# All `MProperty` associated to all `MClassDef` of `mclass`
fun properties(mclass: MClass): Set[MProperty] do
if not self.properties_cache.has_key(mclass) then