Write the Makefile

Property definitions

nitc $ MakefileToolchain :: write_makefile
	# 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
src/compiler/abstract_compiler.nit:355,2--573,4

nitc $ AndroidToolchain :: write_makefile
	redef fun write_makefile(compile_dir, cfiles)
	do
		# Do nothing, already done in `write_files`
	end
src/platform/android.nit:473,2--476,4

nitc $ EnscriptenToolchain :: write_makefile
	redef fun write_makefile(compile_dir, cfiles)
	do
		super

		var emcc_make_flags = "CC=emcc CXX=em++ CFLAGS='-Wno-unused-value -Wno-switch -Qunused-arguments -s ALLOW_MEMORY_GROWTH=1"

		var release = toolcontext.opt_release.value
		if release then
			emcc_make_flags += "' LDFLAGS='--minify 1'"
		else emcc_make_flags += " -g'"

		var make_flags = self.toolcontext.opt_make_flags.value or else ""
		make_flags += emcc_make_flags
		self.toolcontext.opt_make_flags.value = make_flags
	end
src/platform/emscripten.nit:48,2--62,4

nitc $ IOSToolchain :: write_makefile
	redef fun write_makefile(compile_dir, cfiles)
	do
		var project_name = app_project.short_name

		# ---
		# project_folder (source code)

		# Create the plist in the same directory as the generated C code
		if not compile_dir.file_exists then compile_dir.mkdir
		var plist = new PlistTemplate(app_project.name, app_project.namespace,
			app_project.version, app_project.version_code.to_s)
		plist.write_to_file compile_dir/"Info.plist"

		# Copy the folder `ios/AppIcon.appiconset` from the root of the project
		var project_root = "."
		var mpackage = compiler.mainmodule.first_real_mmodule.mpackage
		if mpackage != null then
			var root = mpackage.root
			if root != null then
				var filepath = root.filepath
				if filepath != null then
					project_root = filepath
				end
			end
		end

		# Copy all resources
		var app_files = [project_root]
		app_files.add_all app_project.files

		var icons_found = false

		# Prepare the `Assets.xcassets` folder
		var target_assets_dir = compile_dir / "Assets.xcassets"
		if not target_assets_dir.file_exists then target_assets_dir.mkdir
		"""
{
  "info" : {
	"version" : 1,
	"author" : "nitc"
  }
}""".write_to_file target_assets_dir / "Contents.json"

		(compile_dir / "assets").mkdir

		for path in app_files do

			# Icon
			var icon_dir = path / "ios" / "AppIcon.appiconset"
			if icon_dir.file_exists then
				icons_found = true


				# copy the res folder to the compile dir
				icon_dir = icon_dir.realpath
				toolcontext.exec_and_check(["cp", "-R", icon_dir, target_assets_dir], "iOS project error")
			end

			# Assets
			var assets_dir = path / "assets"
			if assets_dir.file_exists then
				assets_dir = assets_dir.realpath
				toolcontext.exec_and_check(["cp", "-r", assets_dir, compile_dir], "iOS project error")
			end
		end

		# ---
		# project_folder.xcodeproj (projet meta data)

		# Create an XCode project directory
		var dir = ios_project_root/project_name+".xcodeproj"
		if not dir.file_exists then dir.mkdir

		# Create a PBX project file
		var pbx = new PbxprojectTemplate(project_name)

		## Register all source files
		for file in cfiles do pbx.add_file new PbxFile(file)
		for file in compiler.extern_bodies do
			pbx.add_file new PbxFile(file.filename.basename)
		end

		# GC
		if compiler.target_platform.supports_libgc then
			var bdwgc_dir = bdwgc_dir
			assert bdwgc_dir != null

			pbx.cflags = "-I '{bdwgc_dir}/include/' -I '{bdwgc_dir}/libatomic_ops/src' -fno-strict-aliasing " +
			"-DWITH_LIBGC -DNO_EXECUTE_PERMISSION -DALL_INTERIOR_POINTERS -DGC_NO_THREADS_DISCOVERY -DNO_DYLD_BIND_FULLY_IMAGE " +
			"-DGC_DISABLE_INCREMENTAL -DGC_THREADS -DUSE_MMAP -DUSE_MUNMAP -DGC_GCJ_SUPPORT -DJAVA_FINALIZATION "

			var gc_file = new PbxFile("{bdwgc_dir}/extra/gc.c")
			gc_file.cflags = "-Wno-tautological-pointer-compare"
			pbx.add_file gc_file
		end

		# Basic storyboard, mainly to have the right screen size
		var launch_screen_storyboard = new LaunchScreenStoryboardTemplate
		launch_screen_storyboard.title = app_project.name
		launch_screen_storyboard.subtitle = "app.nit"
		launch_screen_storyboard.write_to_file ios_project_root / "LaunchScreen.storyboard"

		# Register the Assets.xcassets folder in the project description
		if icons_found then
			var xcassets = new PbxFile("Assets.xcassets")
			pbx.add_file xcassets
		end

		pbx.write_to_file dir / "project.pbxproj"
	end
src/platform/ios.nit:88,2--197,4