argh what
[QuestHelper.git] / Development / build
blob0994faa830775de6b03307b99d8f8515bc2e5b4e
1 #!/usr/bin/env lua
3 local compile=false
4 local external=true
5 local archive_zip=true
6 local archive_7z=false
7 local icons=true
8 local debug_build=false
9 local changes_lua = true
10 local condense = true
12 function SplitVersion(ver)
13   return string.gsub(ver, " on .*", ""), string.gsub(ver, ".* on ", "")
14 end
16 function AuthorizedVersion(ver)
17   qhv, wowv = SplitVersion(ver)
18   return qhv ~= "Development Version"
19 end
21 function PreWrath(ver)
22   qhv, wowv = SplitVersion(ver)
23   return wowv:sub(1,1) ~= '3'
24 end
26 for i, a in ipairs({...}) do
27   local mode, option = select(3, string.find(a, "^([%+%-]?)(.*)$"))
28   
29   mode = mode ~= "-"
30   
31   if option == "zip" then archive_zip = mode
32   elseif option == "7z" then archive_7z = mode
33   elseif option == "compile" then compile = mode
34   elseif option == "external" then external = mode
35   elseif option == "icons" then icons = mode
36   elseif option == "changes" then changes_lua = mode
37   elseif option == "debug" then debug_build = mode
38   elseif option == "condense" then condense = mode
39   else
40     print("Unknown option: "..option)
41     return 1
42   end
43 end
45 cache = {}
46 local cache_loader = loadfile("build-cache.lua")
47 if cache_loader then
48   cache_loader()
49   cache_loader = nil
50 end
52 if not cache.removed then cache.removed = {} end
53 if not cache.known then cache.known = {} end
54 if not cache.ignored then cache.ignored = {} end
55 if not cache.uid then cache.uid = {} end
57 local file2uid = {}
58 for uid, data in pairs(cache.uid) do file2uid[data.file] = uid end
60 -- so upgrade.lua doesn't freak out
61 GetTime = function () end
62 QuestHelper_File = {}
63 QuestHelper_Loadtime = {}
65 loadfile("dump.lua")()
66 loadfile("fileutil.lua")()
67 QuestHelper = {IsWrath32 = function () return true end}
68 loadfile("../upgrade.lua")()
69 loadfile("compiler.lua")()
70 loadfile("pepperfish.lua")()
71 --loadfile("external.lua")()
73 profiler = newProfiler("time", 1000000)
74 --profiler:start()
76 QuestHelper_BuildZoneLookup()
78 if changes_lua then
79   local stream = io.open("../changes.txt", "r")
80   
81   if stream then
82     local function linkparse(a, b)
83       b = b:gsub("^%s*(.-)%s*$", "%1")
84       if b == "" then
85         return a
86       else
87         return b
88       end
89     end
90     
91     local function totext(text)
92       return text:gsub("%[%[http://www.mandible.net|(.*)%]%]", "%1")
93                  :gsub("%[%s*([^%s%]]+)(.-)%]", linkparse)
94                  :gsub("<code>(.-)</code>", "|cff40bbff%1|r")
95                  :gsub("^%s*==%s*(.-)%s*==%s*$", "|cffffff00       %1|r\n")
96                  :gsub("^%*%*%s", "    • ")
97                  :gsub("^%*%s", "• ")
98                  .."\n"
99     end
100     
101     local count = 0
102     local text = ""
103     
104     while true do
105       line = stream:read("*l")
106       if not line then break end
107       if line:find("^%s*==.*==%s*$") then
108         count = count + 1
109         if count == 6 then
110           break
111         end
112         text = text .. (count == 1 and "" or "\n") .. totext(line)
113       elseif not line:find("^%s*$") then
114         text = text .. totext(line)
115       end
116     end
118     io.close(stream)
119     
120     stream = io.open("../changes.lua", "w")
121     if stream then
122       stream:write(("QuestHelper_ChangeLog=%q\n"):format(text))
123       io.close(stream) 
124     else
125       print("Error writing ../changes.lua")
126     end
127   else
128     print("Error opening ../changes.txt")
129   end
132 if external then
134   --print("Updating Astrolabe.")
135   --FileUtil.updateSVNRepo("http://svn.esamynn.org/astrolabe/branches/wotlk_data_temp", "../Astrolabe")
136   print("Updating ChatThrottleLib.")
137   FileUtil.updateSVNRepo("svn://svn.wowace.com/wow/chatthrottlelib/mainline/trunk", "../ChatThrottleLib")
140 if compile then
141   local function saveCache()
142     local stream = io.open("build-cache.lua", "w")
143     local buffer, prebuf = CreateBuffer(), CreateBuffer()
144     DumpVariable(buffer, prebuf, cache, "cache")
145     stream:write(DumpingComplete(buffer, prebuf))
146     io.close(stream)
147   end
148   
149   local function QuestHelper_IsCorrupted(data)
150     if type(data.QuestHelper_Objectives) == "table" then for version, package in pairs(data.QuestHelper_Objectives) do
151       if AuthorizedVersion(version) and type(package) == "table" then for category, objectives in pairs(package) do
152         if type(objectives) == "table" and PreWrath(version) then for name, objective in pairs(objectives) do
153           if type(objective) == "table" and objective.pos and type(objective.pos) == "table" then for i, pos in pairs(objective.pos) do
154             if pos[1] and pos[1] >= 65 then
155               print("SOMETHING IS SERIOUSLY WRONG, found Wrath coordinates in non-Wrath files (version " .. version .. ", continent " .. pos[1] .. ")")
156               return true
157             end
158           end end
159         end end
160       end end
161     end end
162   end
163   
164   -- Download the latest copy of the translated item/npc names.
165   if external then
166     os.execute("wget http://smariot.hopto.org/wowdata.7z -N")
167     
168     if FileUtil.fileExists("wowdata.7z") then
169       local hash = FileUtil.fileHash("wowdata.7z")
170       if cache.wowdata_hash ~= hash then
171         -- If the archive has changed, extract the lua file from the archive.
172         cache.wowdata_hash = hash
173         os.execute("7z e -y wowdata.7z wowdata.lua")
174       end
175     end
176   end
177   
178   if FileUtil.fileExists("wowdata.lua") then
179     loadfile("wowdata.lua")()
180   end
182   local all_input_files, unknown_input_files = {}, {}
184   local since_last_save = 0
186   FileUtil.forEachFile("LocalInput", function (name)
187     if FileUtil.extension(name) == "lua" or 
188        FileUtil.extension(name) == "bak" then
189        
190       local hash = FileUtil.fileHash(name)
191       if cache.ignored[hash] then return end
193       if since_last_save == 100 then
194         print("Collecting garbage and saving . . .")
195         collectgarbage("collect")
196         saveCache()
197         since_last_save = 0
198       end
199       since_last_save = since_last_save + 1
200     
201       cache.ignored[hash] = true
202       
203       local input = loadfile(name)
204       if input then
205         local data = {}
206         setfenv(input, data)
207         if pcall(input) then
208           if not (data.QuestHelper_Locale and data.QuestHelper_Objectives) then
209             print("! "..name.." isn't a QuestHelper SavedVariables file.")
210             return
211           end
212           
213           local upgradable_data = {}
214           setfenv(input, upgradable_data)
215           input()
216           QuestHelper_UpgradeDatabase(upgradable_data)
217           if QuestHelper_IsPolluted(upgradable_data) then
218             print("!! "..name.." is polluted")
219             return
220           end
221           if QuestHelper_IsCorrupted(upgradable_data) then
222             print("!! "..name.." is corrupted")
223             return
224           end
225           if not upgradable_data.QuestHelper_UID then
226             print("!! "..name.." has no UID")
227             return
228           end
229           
230           local tempname = os.tmpname()
231           local stream = io.open(tempname, "w")
232           if stream then
233             print("Copying/Sorting "..name)
234             stream:write(ScanAndDumpVariable(data, nil, true) or "")
235             io.close(stream)
236             hash = FileUtil.fileHash(tempname)
237             if hash then
238               local input_name = "Input/"..hash..".lua"
239               if not cache.removed[input_name] then
240                 if not cache.known[input_name] then
241                   FileUtil.copyFile(tempname, input_name)
242                   unknown_input_files[input_name] = name
243                 else
244                   cache.known[input_name] = name
245                 end
246               else
247                 cache.removed[input_name] = name
248               end
249             else
250               print("!!! Can't get hash of "..tempname..", for "..name)
251             end
252             FileUtil.unlinkFile(tempname)
253           end
254         else
255           print("!!! "..name.." couldn't be executed.")
256         end
257       else
258         print("!!! "..name.." couldn't be loaded.")
259       end
260     end
261   end)
263   collectgarbage("collect")
264   saveCache()
265   
266   FileUtil.forEachFile("Input", function (name)
267     if cache.removed[name] then
268       print("!!! Obsolete: ", cache.removed[name].." ("..name..")")
269       os.remove(name)
270     else
271       if not cache.known[name] then
272         unknown_input_files[name] = unknown_input_files[name] or name
273       end
274       
275       all_input_files[name] = cache.known[name] or unknown_input_files[name]
276     end
277   end)
279   local function ProcessObjective(category, name, objective, result)
280     local istring = "obj."..category.."."..name
281     
282     if category ~= "item" then
283       local seen = 0
284       if objective.pos then for i, pos in pairs(objective.pos) do
285         seen = seen + pos[4]
286       end end
287       
288       result[istring..".seen"] = (result[istring..".seen"] or 0) + seen
289     end
290     
291     if objective.vendor then
292       result[istring..".vend"] = (result[istring..".vend"] or 0) + #objective.vendor
293     end
294     
295     if objective.drop then for monster, count in pairs(objective.drop) do
296       result[istring] = (result[istring] or 0) + count
297     end end
298   end
300   local function ProcessQuest(faction, level, name, quest, result)
301     local qstring = "quest."..faction.."."..level.."."..name
302     result[qstring] = (result[qstring] or 0)+((quest.finish or quest.pos) and 1 or 0)
303     
304     if quest.item then for item_name, data in pairs(quest.item) do
305       ProcessObjective("item", item_name, data, result)
306     end end
307     
308     if quest.alt then for _, quest2 in pairs(quest.alt) do
309       ProcessQuest(faction, level, name, quest2, result)
310     end end
311   end
312   
313   local function LoadFile(file)
314     local data = loadfile(file)
315     local result = {}
316     if data then
317       local loaded = {}
318       if not pcall(setfenv(data, loaded)) then
319         print("!!!!! oh god something is wrong "..file)
320         return
321       end
322          
323       data()
324       
325       QuestHelper_UpgradeDatabase(loaded)
326       
327       if loaded.QuestHelper_UID then
328         result.uid = loaded.QuestHelper_UID
329         result.time = loaded.QuestHelper_SaveDate
330       else
331         if type(loaded.QuestHelper_Quests) == "table" then for version, package in pairs(loaded.QuestHelper_Quests) do
332           if AuthorizedVersion(version) and type(package) == "table" then for faction, levels in pairs(package) do
333             if type(levels) == "table" then for level, quest_list in pairs(levels) do
334               if type(quest_list) == "table" then for name, quest in pairs(quest_list) do
335                 ProcessQuest(faction, level, name, quest, result)
336               end end
337             end end
338           end end
339         end end
340         
341         if type(loaded.QuestHelper_Objectives) == "table" then for version, package in pairs(loaded.QuestHelper_Objectives) do
342           if AuthorizedVersion(version) and type(package) == "table" then for faction, levels in pairs(package) do
343             if type(objectives) == "table" then for name, objective in pairs(objectives) do
344               ProcessObjective(category, name, objective, result)
345             end end
346           end end
347         end end
348       end
349     end
350     
351     return result
352   end
354   local function ObsoletedBy(data1, data2)
355     if data1.uid or data2.uid then
356       return data1.loc == data2.loc and data1.uid == data2.uid and (data1.time or 0) >= (data2.time or 0)
357     end
358     
359     for key, value in pairs(data1) do
360       local value2 = data2[key]
361       if value2 == nil or value2 < value then
362         return false
363       end
364     end
365     return true
366   end
368   local checked = {}
369   local file_data = {}
370   
371   -- This entire mess is built to find obsolete files and remove them.
372   for new_name, original_name in pairs(unknown_input_files) do
373     print("Checking: ", original_name)
374     local data = file_data[new_name]
375     if not data then
376       data = LoadFile(new_name)
377       file_data[new_name] = data
378     end
379     cache.known[new_name] = original_name
380     checked[new_name] = true
381     
382     if data then
383       local uid, last_save = data.uid, data.time
384       
385       if uid then
386         local existing = cache.uid[uid]
387         if not existing then
388           cache.uid[uid] = {file=new_name, save=last_save}
389           file2uid[new_name] = uid
390         else
391           if existing.save >= last_save then
392             print("!!! Obsolete: ", original_name)
393             print("!!!       By: ", all_input_files[existing.file])
394             print("")
395             os.remove(new_name)
396             file_data[new_name] = nil
397             all_input_files[new_name] = nil
398             cache.removed[new_name] = original_name
399             cache.known[new_name] = nil
400             unknown_input_files[new_name] = nil
401           else
402             print("!!! Obsolete: ", all_input_files[existing.file])
403             print("!!!       By: ", original_name)
404             print("")
405             
406             os.remove(existing.file)
407             file_data[existing.file] = nil
408             file2uid[existing.file] = nil
409             file2uid[new_name] = uid
410             cache.removed[existing.file] = all_input_files[existing.file]
411             all_input_files[existing.file] = nil
412             cache.known[existing.file] = nil
413             existing.file = new_name
414           end
415         end
416       else
417         for existing_name, existing_original_name in pairs(all_input_files) do
418           if not checked[existing_name] and not file2uid[existing_name] then
419             local data2 = file_data[existing_name]
420             if not data2 then
421               data2 = LoadFile(existing_name)
422               file_data[existing_name] = data2
423             end
424             
425             if data2 and not data2.uid then
426               if ObsoletedBy(data, data2) then
427                 print("!!! Obsolete: ", original_name)
428                 print("!!!       By: ", existing_original_name)
429                 print("")
430                 
431                 os.remove(new_name)
432                 file_data[new_name] = nil
433                 all_input_files[new_name] = nil
434                 cache.removed[new_name] = original_name
435                 cache.known[new_name] = nil
436                 unknown_input_files[new_name] = nil
437                 break
438               elseif ObsoletedBy(data2, data) then
439                 print("!!! Obsolete: ", existing_original_name)
440                 print("!!!       By: ", original_name)
441                 print("")
442                 
443                 os.remove(existing_name)
444                 file_data[existing_name] = nil
445                 all_input_files[existing_name] = nil
446                 cache.removed[existing_name] = existing_original_name
447                 cache.known[existing_name] = nil
448                 unknown_input_files[existing_name] = nil
449               end
450             end
451           end
452         end
453       end
454     end
455   end
456   
457   checked, file_data = nil, nil
458   
459   saveCache()
460   collectgarbage("collect")
462   --print("Compiling Lightheaded/eql3 data. . .")
463   --ProcessExternal()
464   
465   local total_count = 0
466   local counter = 0
467   local pairfrequencies = {}
468   for name, origin in pairs(all_input_files) do
469     total_count = total_count + 1       -- I fuckin' hate Lua
470   end
471   for name, origin in pairs(all_input_files) do
472     counter = counter + 1
473     print("Compiling " .. counter .. "/" .. total_count .. ": ", origin)
474     --CompileInputFile(name)
475     local ret, msg = pcall(CompileInputFile, name, pairfrequencies)
476     if not ret then
477       print("!!!!! FAILURE")
478       print(msg)
479     end
480   end
481   
482   print("Processing final data")
483   local finaldata = CompileFinish()
484   
485   local finalstatic = io.open("../static.lua", "w")
486   finalstatic:write("QuestHelper_File[\"static.lua\"] = \"Development Version\"\n")
487   finalstatic:write("QuestHelper_Loadtime[\"static.lua\"] = GetTime()\n")
488   finalstatic:write("QuestHelper_StaticData={")
489   
490   for k, v in pairs(finaldata) do
491     collectgarbage("collect")
492     local foloc = string.format("static_%s.lua", k)
493     local finalout = "../" .. foloc
494     local tempout = finalout
495     if condense then tempout = string.format("static_%s.large", k) end
496     
497     print("Writing: " .. tempout)
498     local stream = io.open(tempout, "w")
499     stream:write(string.format("QuestHelper_File[\"%s\"] = \"Development Version\"\n", foloc))
500     stream:write(string.format("QuestHelper_Loadtime[\"%s\"] = GetTime()\n", foloc))
501     stream:write(string.format("if GetLocale() ~= \"%s\" then return end\n", k))  -- wellp
502     stream:write(ScanAndDumpVariable(v, string.format("QuestHelper_StaticData_%s", k)))
503     io.close(stream)
504     
505     if condense then
506       print("Condensing " .. finalout)
507       assert(os.execute(string.format("lua LuaSrcDiet.lua --maximum %s -o %s", tempout, finalout)) ~= -1)
508       if false then
509         local data_loader = loadfile("LuaSrcDiet.lua")
510         local data = {arg={"--maximum", tempout, "-o", finalout}, TEST=true, string=string, print=print, table=table, ipairs=ipairs, error=error, io=io, tonumber=tonumber --[[ assert=assert, FileUtil=FileUtil, tostring=tostring ]]}  -- there has to be a better way to do this
511         setfenv(data_loader, data)
512         data_loader()
513         data.main()
514       end
515     end
516     
517     finalstatic:write(string.format("  %s=QuestHelper_StaticData_%s,", k, k))
518     --assert(FileUtil.fileContains("../QuestHelper.toc", foloc), "didn't contain " .. foloc)
519   end
520   
521   finalstatic:write("}")
522   io.close(finalstatic)
523   
524   local qhwowv = {}
525   local qhfrequencies = {}
526   local wowfrequencies = {}
527   
528   for k, v in pairs(pairfrequencies) do
529     table.insert(qhwowv, {freq = v, tag = k})
530     local qh, wow = SplitVersion(k)
531     qhfrequencies[qh] = (qhfrequencies[qh] or 0) + v
532     wowfrequencies[wow] = (wowfrequencies[wow] or 0) + v
533   end
534   
535   local qhv = {}
536   local wowv = {}
537   
538   for k, v in pairs(qhfrequencies) do table.insert(qhv, {freq = v, tag = k}) end
539   for k, v in pairs(wowfrequencies) do table.insert(wowv, {freq = v, tag = k}) end
540   
541   table.sort(qhwowv, function (a, b) return a.freq < b.freq end)
542   table.sort(qhv, function (a, b) return a.freq < b.freq end)
543   table.sort(wowv, function (a, b) return a.freq < b.freq end)
545   for k, v in pairs(qhwowv) do
546     print(string.format("%d: %s", v.freq, v.tag))
547   end
548   print("")
549   for k, v in pairs(qhv) do
550     print(string.format("%d: %s", v.freq, v.tag))
551   end
552   print("")
553   for k, v in pairs(wowv) do
554     print(string.format("%d: %s", v.freq, v.tag))
555   end
556 elseif not FileUtil.fileExists("../static.lua") then
557   print("../static.lua doesn't exist; you can't skip the compile step.")
558   return 1
561 if icons then
562   print("Creating: Icons.tga")
563   --if not FileUtil.fileExists("../Art/Icons.tga") then
564   --  print("You'll need to manually create Art/Icons.tga, ImageMagick's SVG support seems to have been broken recently.")
565   --end
566   FileUtil.convertImage("Data/art.svg", "../Art/Icons.tga")
569 local version_string = "UNKNOWN"
571 if archive_zip or archive_7z then
572   FileUtil.unlinkDirectory("QuestHelper")
573   
574   FileUtil.createDirectory("QuestHelper")
575   FileUtil.createDirectory("QuestHelper/Art")
576   FileUtil.createDirectory("QuestHelper/Fonts")
577   
578   local info = nil
579   
580   -- Try to coax git to give us something to use for the version.
581   local stream = io.popen("git describe --tags HEAD", "r")
582   if stream then
583     info = stream:read("*line")
584     if info then
585       -- Replace the first dash with a dot.
586       info = string.gsub(info, "^(.*)%-(.*)%-g(.*)$", "%1%.%2%-%3", 1)
587     end
588     io.close(stream)
589   end
590   
591   if not info then
592     -- Didn't get a nice looking tag to use, try to at least get a hash for the version.
593     stream = io.popen("git-log -1 --pretty=\"format:%2-%h\"", "r")
594     if stream then
595       info = stream:read("*line")
596       io.close(stream)
597     end
598   end
599   
600   if not info then
601     -- We have no idea what version this is, will use the string already in the TOC.
602     info = "%2"
603   end
604   
605   FileUtil.copyFile("../QuestHelper.toc", "QuestHelper", "^(##%s-Version%s-):%s*(.*)%s*$",
606     function (a, b)
607       version_string = string.gsub(info, "%%2", b) return a..": "..version_string
608     end)
609   
610   local file_version_pattern = "%1"..string.format("%q", version_string)
611   
612   FileUtil.forEachFile("..", function (file)
613     local ext = FileUtil.extension(file)
614     if ext == "lua" or ext == "xml" then
615       if debug_build then
616         FileUtil.copyFile(file, "QuestHelper")
617       else
618         FileUtil.copyFile(file, "QuestHelper",
619                                                "(assert%s*%b())", "--[[ %1 ]]",
620                                                "(QuestHelper:Assert%s*%b())", "--[[ %1 ]]",
621                                                "(QuestHelper_File%[[^%]]*%]%s*=%s*)\"[^\"]+\"", file_version_pattern)
622       end
623     end
624   end)
625   
626   FileUtil.forEachFile("../Art", function (file)
627     local ext = FileUtil.extension(file)
628     if ext == "blp" or ext == "tga" then
629       FileUtil.copyFile(file, "QuestHelper/Art")
630     end
631   end)
632   
633   FileUtil.forEachFile("../Fonts", function (file)
634     local ext = FileUtil.extension(file)
635     if ext == "txt" or ext == "ttf" then
636       FileUtil.copyFile(file, "QuestHelper/Fonts")
637     end
638   end)
639   
640   for i, dir in ipairs({"AstrolabeQH", "ChatThrottleLib", "LibAboutPanel", "lang"}) do
641     FileUtil.copyDirectoryRecursively("../"..dir, "QuestHelper/"..dir)
642   end
643   
644   FileUtil.copyFile("../AstrolabeQH/Astrolabe.lua", "QuestHelper/AstrolabeQH", "(QuestHelper_File%[[^%]]*%]%s*=%s*)\"[^\"]+\"", file_version_pattern)
645   FileUtil.copyFile("../AstrolabeQH/AstrolabeMapMonitor.lua", "QuestHelper/AstrolabeQH", "(QuestHelper_File%[[^%]]*%]%s*=%s*)\"[^\"]+\"", file_version_pattern)
646   FileUtil.copyFile("../AstrolabeQH/DongleStub.lua", "QuestHelper/AstrolabeQH", "(QuestHelper_File%[[^%]]*%]%s*=%s*)\"[^\"]+\"", file_version_pattern)
647   
648   FileUtil.copyFile("../MinimapArrow.tga", "QuestHelper")
649   FileUtil.copyFile("../arrow_image.blp", "QuestHelper")
650   FileUtil.copyFile("../arrow_image_down.blp", "QuestHelper")
651   FileUtil.copyFile("../triangle.tga", "QuestHelper")
652   FileUtil.copyFile("../line.tga", "QuestHelper")
653   
654   if archive_zip then
655     local archive = "../QuestHelper-"..version_string..".zip"
656     print("Creating "..archive)
657     FileUtil.unlinkFile(archive)
658     FileUtil.createZipArchive("QuestHelper", archive)
659   end
660   
661   if archive_7z then
662     local archive = "../QuestHelper-"..version_string..".7z"
663     print("Creating "..archive)
664     FileUtil.unlinkFile(archive)
665     FileUtil.create7zArchive("QuestHelper", archive)
666   end
667   
668   FileUtil.unlinkDirectory("QuestHelper")
671 print("Done!")
673 --profiler:stop()
674 --local outfile = io.open("profile.txt", "w+")
675 --profiler:report(outfile)
676 --outfile:close()