1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
lfs = require "lfs"
local cxx = "clang++ -c"
local lxx = "clang++"
local ar = "ar rcs"
function compile( file, outfile, flags )
	local flag_str = ""
	for k, v in pairs(flags ) do
		flag_str = flag_str..v.." "
	end
	os.execute( cxx.." "..flag_str.." -o "..outfile.." "..file )
end
function link( files, outfile, flags )
	local flag_str = ""
	for k, v in pairs(flags) do
		flag_str = flag_str..v.." "
	end
	local file_str = ""
	for k,v in pairs(files) do
		file_str = file_str..v.." "
	end
	os.execute( lxx.." "..flag_str.." -o "..outfile.." "..file_str)
end
function archive( files, outfile )
	local file_str = ""
	for k,v in pairs( files ) do
		file_str = file_str..v.." "
	end
	os.execute( ar.." "..outfile.." "..file_str )
end
function is_file_newer( file1, file2 )
	local file1_mod = lfs.attributes( file1, "modification" )
	local file2_mod = lfs.attributes( file2, "modification" )
	if file1_mod ~= nil and file2_mod ~= nil then
		return file1_mod > file2_mod
	else
		return nil
	end
end
function logify_path( path )
	local modded = string.gsub( path, "/+", "/" )
	return modded
end
function merge_tables_k( tbl1, tbl2 )
	local merged = {}
	for k, _ in pairs( tbl1 ) do
		merged[k] = 1
	end
	for k, _ in pairs( tbl2 ) do
		merged[k] = 1
	end
	return merged
end
function cpp_header_dependencies( file, inc_dirs, prev_done )
	local filenames = {}
	for line in io.lines( file ) do
		if string.sub( line, 1, 8 ) == "#include" then
			local modded = string.gsub( line, "#include%s*<","" )
			modded = string.gsub( modded, ">.*", "" )
			filenames[modded] = 1
		end
	end
	
	local fpath_headers = {}
	
	if prev_done ~= nil then
		for _,dir in pairs( inc_dirs ) do
			for fname, _ in pairs(filenames) do
				if lfs.attributes(dir.."/"..fname) ~= nil then
					local logified = logify_path( dir.."/"..fname)
					if not prev_done[logified] then
						fpath_headers[logified] = 1
					end
				end
			end
		end
	else
		for _,dir in pairs( inc_dirs ) do
			for fname, _ in pairs(filenames) do
				if lfs.attributes(dir.."/"..fname) ~= nil then
					local logified = logify_path( dir.."/"..fname)
					fpath_headers[logified] = 1
				end
			end
		end
	end
	-- merge fpath_headers and prev_done
	local merged = {}
	if prev_done ~= nil then
		merged = merge_tables_k( fpath_headers, prev_done )
	else
		merged = fpath_headers
	end
	
	for flap,_ in pairs( fpath_headers ) do
		merged = merge_tables_k( merged, cpp_header_dependencies(flap,inc_dirs, merged) )
	end
	if prev_done ~= nil then
		return merged
	else
		local newheaders = {}
		for k,_ in pairs(merged) do
			table.insert( newheaders, k )
		end
		return newheaders
	end
end
function obj_needs_update(ofile, src_file, inc_dirs)
	local deps = cpp_header_dependencies( src_file, inc_dirs )
	table.insert( deps, src_file )
	local newest = 0
	for _, v in pairs( deps ) do
		local modtime = lfs.attributes( v, "modification" )
		if modtime > newest then
			newest = modtime
		end
	end
	local ret = false
	local ofile_modtime = lfs.attributes(ofile,"modification")
	if ofile_modtime then
		return newest > ofile_modtime
	else
		return true
	end
end
function bin_path_from_src( path )
	local modded = string.gsub( path, "%.cpp", "" )
	modded = string.gsub( modded, "%./", "" )
	modded = string.gsub( modded, "/", "__" )
	return modded..".o"
end
function unit_test_path_from_src( path )
	local modded = string.gsub( path, "/%w+%.cpp", "" )
	modded = string.gsub( modded, "%./", "%./module_tests/" )
	return modded
end
local debug_mode_cflags = { "-Wall", "-ggdb", "-I ./inc" }
local normal_cflags = { "-Wall", "-I ./inc" }
local src_dir = "./src"
local bin_dir = "./bin"
local inc_dir = "./inc"
-- change later so i can specify debugmode or normal, or whatever
local cflags = debug_mode_cflags
--load all source files that need to be compiled into a table
local src_files = {}
function load_src_files(dir)
	for entry in lfs.dir(dir) do
		if entry ~= "." and entry ~= ".." then
			local fpath = dir.."/"..entry
			local atr = lfs.attributes( fpath )
			assert( type(atr) == "table" )
			if atr.mode == "directory" then
				load_src_files(fpath)
			else
				if string.sub( fpath, fpath:len() - 3, fpath:len() ) == ".cpp" then
					table.insert( src_files, fpath )
				end
			end
		end
	end
end
load_src_files( src_dir )
--compile and schedule unit tests for the src files that have changed
local unit_test_schedule = {}
for i = 1,#src_files do
	obj_file = bin_dir.."/"..bin_path_from_src( src_files[i] )
	if obj_needs_update( obj_file, src_files[i], { inc_dir } ) then
		compile( src_files[i], obj_file, cflags )
		unit_test_schedule[ unit_test_path_from_src( src_files[i] ) ] = 1
	end
end
--chuck all them o's into an .a
local obj_file_list = ""
for i=1,#src_files do
	obj_file_list = obj_file_list.." "..bin_dir.."/"..bin_path_from_src( src_files[i] )
end
os.execute("ar rcs "..bin_dir.."/librevengine.a "..obj_file_list)
-- run unit tests
for k,_ in pairs( unit_test_schedule ) do
	local test_func = dofile( k.."/test.lua" )
	if not test_func(k) then
		print( "Failed unit test at: "..k )
	end
end