8 local debug_build=false
9 local changes_lua = true
12 function SplitVersion(ver)
13 return string.gsub(ver, " on .*", ""), string.gsub(ver, ".* on ", "")
16 function AuthorizedVersion(ver)
17 qhv, wowv = SplitVersion(ver)
18 return qhv ~= "Development Version"
21 function PreWrath(ver)
22 qhv, wowv = SplitVersion(ver)
23 return wowv:sub(1,1) ~= '3'
26 for i, a in ipairs({...}) do
27 local mode, option = select(3, string.find(a, "^([%+%-]?)(.*)$"))
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
40 print("Unknown option: "..option)
46 local cache_loader = loadfile("build-cache.lua")
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
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
63 QuestHelper_Loadtime = {}
65 loadfile("dump.lua")()
66 loadfile("fileutil.lua")()
67 loadfile("../upgrade.lua")()
68 loadfile("compiler.lua")()
69 loadfile("pepperfish.lua")()
70 --loadfile("external.lua")()
72 profiler = newProfiler("time", 1000000)
75 QuestHelper_BuildZoneLookup()
78 local stream = io.open("../changes.txt", "r")
81 local function linkparse(a, b)
82 b = b:gsub("^%s*(.-)%s*$", "%1")
90 local function totext(text)
91 return text:gsub("%[%[http://www.mandible.net|(.*)%]%]", "%1")
92 :gsub("%[%s*([^%s%]]+)(.-)%]", linkparse)
93 :gsub("<code>(.-)</code>", "|cff40bbff%1|r")
94 :gsub("^%s*==%s*(.-)%s*==%s*$", "|cffffff00 %1|r\n")
95 :gsub("^%*%*%s", " • ")
104 line = stream:read("*l")
105 if not line then break end
106 if line:find("^%s*==.*==%s*$") then
111 text = text .. (count == 1 and "" or "\n") .. totext(line)
112 elseif not line:find("^%s*$") then
113 text = text .. totext(line)
119 stream = io.open("../changes.lua", "w")
121 stream:write(("QuestHelper_ChangeLog=%q\n"):format(text))
124 print("Error writing ../changes.lua")
127 print("Error opening ../changes.txt")
133 --print("Updating Astrolabe.")
134 --FileUtil.updateSVNRepo("http://svn.esamynn.org/astrolabe/branches/wotlk_data_temp", "../Astrolabe")
135 print("Updating ChatThrottleLib.")
136 FileUtil.updateSVNRepo("svn://svn.wowace.com/wow/chatthrottlelib/mainline/trunk", "../ChatThrottleLib")
140 local function saveCache()
141 local stream = io.open("build-cache.lua", "w")
142 local buffer, prebuf = CreateBuffer(), CreateBuffer()
143 DumpVariable(buffer, prebuf, cache, "cache")
144 stream:write(DumpingComplete(buffer, prebuf))
148 local function QuestHelper_IsCorrupted(data)
149 if type(data.QuestHelper_Objectives) == "table" then for version, package in pairs(data.QuestHelper_Objectives) do
150 if AuthorizedVersion(version) and type(package) == "table" then for category, objectives in pairs(package) do
151 if type(objectives) == "table" and PreWrath(version) then for name, objective in pairs(objectives) do
152 if type(objective) == "table" and objective.pos and type(objective.pos) == "table" then for i, pos in pairs(objective.pos) do
153 if pos[1] and pos[1] >= 65 then
154 print("SOMETHING IS SERIOUSLY WRONG, found Wrath coordinates in non-Wrath files (version " .. version .. ", continent " .. pos[1] .. ")")
163 -- Download the latest copy of the translated item/npc names.
165 os.execute("wget http://smariot.hopto.org/wowdata.7z -N")
167 if FileUtil.fileExists("wowdata.7z") then
168 local hash = FileUtil.fileHash("wowdata.7z")
169 if cache.wowdata_hash ~= hash then
170 -- If the archive has changed, extract the lua file from the archive.
171 cache.wowdata_hash = hash
172 os.execute("7z e -y wowdata.7z wowdata.lua")
177 if FileUtil.fileExists("wowdata.lua") then
178 loadfile("wowdata.lua")()
181 local all_input_files, unknown_input_files = {}, {}
183 local since_last_save = 0
185 FileUtil.forEachFile("LocalInput", function (name)
186 if FileUtil.extension(name) == "lua" or
187 FileUtil.extension(name) == "bak" then
189 local hash = FileUtil.fileHash(name)
190 if cache.ignored[hash] then return end
192 if since_last_save == 100 then
193 print("Collecting garbage and saving . . .")
194 collectgarbage("collect")
198 since_last_save = since_last_save + 1
200 cache.ignored[hash] = true
202 local input = loadfile(name)
207 if not (data.QuestHelper_Locale and data.QuestHelper_Objectives) then
208 print("! "..name.." isn't a QuestHelper SavedVariables file.")
212 local upgradable_data = {}
213 setfenv(input, upgradable_data)
215 QuestHelper_UpgradeDatabase(upgradable_data)
216 if QuestHelper_IsPolluted(upgradable_data) then
217 print("!! "..name.." is polluted")
220 if QuestHelper_IsCorrupted(upgradable_data) then
221 print("!! "..name.." is corrupted")
224 if not upgradable_data.QuestHelper_UID then
225 print("!! "..name.." has no UID")
229 local tempname = os.tmpname()
230 local stream = io.open(tempname, "w")
232 print("Copying/Sorting "..name)
233 stream:write(ScanAndDumpVariable(data, nil, true) or "")
235 hash = FileUtil.fileHash(tempname)
237 local input_name = "Input/"..hash..".lua"
238 if not cache.removed[input_name] then
239 if not cache.known[input_name] then
240 FileUtil.copyFile(tempname, input_name)
241 unknown_input_files[input_name] = name
243 cache.known[input_name] = name
246 cache.removed[input_name] = name
249 print("!!! Can't get hash of "..tempname..", for "..name)
251 FileUtil.unlinkFile(tempname)
254 print("!!! "..name.." couldn't be executed.")
257 print("!!! "..name.." couldn't be loaded.")
262 collectgarbage("collect")
265 FileUtil.forEachFile("Input", function (name)
266 if cache.removed[name] then
267 print("!!! Obsolete: ", cache.removed[name].." ("..name..")")
270 if not cache.known[name] then
271 unknown_input_files[name] = unknown_input_files[name] or name
274 all_input_files[name] = cache.known[name] or unknown_input_files[name]
278 local function ProcessObjective(category, name, objective, result)
279 local istring = "obj."..category.."."..name
281 if category ~= "item" then
283 if objective.pos then for i, pos in pairs(objective.pos) do
287 result[istring..".seen"] = (result[istring..".seen"] or 0) + seen
290 if objective.vendor then
291 result[istring..".vend"] = (result[istring..".vend"] or 0) + #objective.vendor
294 if objective.drop then for monster, count in pairs(objective.drop) do
295 result[istring] = (result[istring] or 0) + count
299 local function ProcessQuest(faction, level, name, quest, result)
300 local qstring = "quest."..faction.."."..level.."."..name
301 result[qstring] = (result[qstring] or 0)+((quest.finish or quest.pos) and 1 or 0)
303 if quest.item then for item_name, data in pairs(quest.item) do
304 ProcessObjective("item", item_name, data, result)
307 if quest.alt then for _, quest2 in pairs(quest.alt) do
308 ProcessQuest(faction, level, name, quest2, result)
312 local function LoadFile(file)
313 local data = loadfile(file)
317 if not pcall(setfenv(data, loaded)) then
318 print("!!!!! oh god something is wrong "..file)
324 QuestHelper_UpgradeDatabase(loaded)
326 if loaded.QuestHelper_UID then
327 result.uid = loaded.QuestHelper_UID
328 result.time = loaded.QuestHelper_SaveDate
330 if type(loaded.QuestHelper_Quests) == "table" then for version, package in pairs(loaded.QuestHelper_Quests) do
331 if AuthorizedVersion(version) and type(package) == "table" then for faction, levels in pairs(package) do
332 if type(levels) == "table" then for level, quest_list in pairs(levels) do
333 if type(quest_list) == "table" then for name, quest in pairs(quest_list) do
334 ProcessQuest(faction, level, name, quest, result)
340 if type(loaded.QuestHelper_Objectives) == "table" then for version, package in pairs(loaded.QuestHelper_Objectives) do
341 if AuthorizedVersion(version) and type(package) == "table" then for faction, levels in pairs(package) do
342 if type(objectives) == "table" then for name, objective in pairs(objectives) do
343 ProcessObjective(category, name, objective, result)
353 local function ObsoletedBy(data1, data2)
354 if data1.uid or data2.uid then
355 return data1.loc == data2.loc and data1.uid == data2.uid and (data1.time or 0) >= (data2.time or 0)
358 for key, value in pairs(data1) do
359 local value2 = data2[key]
360 if value2 == nil or value2 < value then
370 -- This entire mess is built to find obsolete files and remove them.
371 for new_name, original_name in pairs(unknown_input_files) do
372 print("Checking: ", original_name)
373 local data = file_data[new_name]
375 data = LoadFile(new_name)
376 file_data[new_name] = data
378 cache.known[new_name] = original_name
379 checked[new_name] = true
382 local uid, last_save = data.uid, data.time
385 local existing = cache.uid[uid]
387 cache.uid[uid] = {file=new_name, save=last_save}
388 file2uid[new_name] = uid
390 if existing.save >= last_save then
391 print("!!! Obsolete: ", original_name)
392 print("!!! By: ", all_input_files[existing.file])
395 file_data[new_name] = nil
396 all_input_files[new_name] = nil
397 cache.removed[new_name] = original_name
398 cache.known[new_name] = nil
399 unknown_input_files[new_name] = nil
401 print("!!! Obsolete: ", all_input_files[existing.file])
402 print("!!! By: ", original_name)
405 os.remove(existing.file)
406 file_data[existing.file] = nil
407 file2uid[existing.file] = nil
408 file2uid[new_name] = uid
409 cache.removed[existing.file] = all_input_files[existing.file]
410 all_input_files[existing.file] = nil
411 cache.known[existing.file] = nil
412 existing.file = new_name
416 for existing_name, existing_original_name in pairs(all_input_files) do
417 if not checked[existing_name] and not file2uid[existing_name] then
418 local data2 = file_data[existing_name]
420 data2 = LoadFile(existing_name)
421 file_data[existing_name] = data2
424 if data2 and not data2.uid then
425 if ObsoletedBy(data, data2) then
426 print("!!! Obsolete: ", original_name)
427 print("!!! By: ", existing_original_name)
431 file_data[new_name] = nil
432 all_input_files[new_name] = nil
433 cache.removed[new_name] = original_name
434 cache.known[new_name] = nil
435 unknown_input_files[new_name] = nil
437 elseif ObsoletedBy(data2, data) then
438 print("!!! Obsolete: ", existing_original_name)
439 print("!!! By: ", original_name)
442 os.remove(existing_name)
443 file_data[existing_name] = nil
444 all_input_files[existing_name] = nil
445 cache.removed[existing_name] = existing_original_name
446 cache.known[existing_name] = nil
447 unknown_input_files[existing_name] = nil
456 checked, file_data = nil, nil
459 collectgarbage("collect")
461 --print("Compiling Lightheaded/eql3 data. . .")
464 local total_count = 0
466 local pairfrequencies = {}
467 for name, origin in pairs(all_input_files) do
468 total_count = total_count + 1 -- I fuckin' hate Lua
470 for name, origin in pairs(all_input_files) do
471 counter = counter + 1
472 print("Compiling " .. counter .. "/" .. total_count .. ": ", origin)
473 --CompileInputFile(name)
474 local ret, msg = pcall(CompileInputFile, name, pairfrequencies)
476 print("!!!!! FAILURE")
481 print("Processing final data")
482 local finaldata = CompileFinish()
484 local finalstatic = io.open("../static.lua", "w")
485 finalstatic:write("QuestHelper_File[\"static.lua\"] = \"Development Version\"\n")
486 finalstatic:write("QuestHelper_Loadtime[\"static.lua\"] = GetTime()\n")
487 finalstatic:write("QuestHelper_StaticData={")
489 for k, v in pairs(finaldata) do
490 collectgarbage("collect")
491 local foloc = string.format("static_%s.lua", k)
492 local finalout = "../" .. foloc
493 local tempout = finalout
494 if condense then tempout = string.format("static_%s.large", k) end
496 print("Writing: " .. tempout)
497 local stream = io.open(tempout, "w")
498 stream:write(string.format("QuestHelper_File[\"%s\"] = \"Development Version\"\n", foloc))
499 stream:write(string.format("QuestHelper_Loadtime[\"%s\"] = GetTime()\n", foloc))
500 stream:write(string.format("if GetLocale() ~= \"%s\" then return end\n", k)) -- wellp
501 stream:write(ScanAndDumpVariable(v, string.format("QuestHelper_StaticData_%s", k)))
505 print("Condensing " .. finalout)
506 assert(os.execute(string.format("lua LuaSrcDiet.lua --maximum %s -o %s", tempout, finalout)) ~= -1)
508 local data_loader = loadfile("LuaSrcDiet.lua")
509 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
510 setfenv(data_loader, data)
516 finalstatic:write(string.format(" %s=QuestHelper_StaticData_%s,", k, k))
517 --assert(FileUtil.fileContains("../QuestHelper.toc", foloc), "didn't contain " .. foloc)
520 finalstatic:write("}")
521 io.close(finalstatic)
524 local qhfrequencies = {}
525 local wowfrequencies = {}
527 for k, v in pairs(pairfrequencies) do
528 table.insert(qhwowv, {freq = v, tag = k})
529 local qh, wow = SplitVersion(k)
530 qhfrequencies[qh] = (qhfrequencies[qh] or 0) + v
531 wowfrequencies[wow] = (wowfrequencies[wow] or 0) + v
537 for k, v in pairs(qhfrequencies) do table.insert(qhv, {freq = v, tag = k}) end
538 for k, v in pairs(wowfrequencies) do table.insert(wowv, {freq = v, tag = k}) end
540 table.sort(qhwowv, function (a, b) return a.freq < b.freq end)
541 table.sort(qhv, function (a, b) return a.freq < b.freq end)
542 table.sort(wowv, function (a, b) return a.freq < b.freq end)
544 for k, v in pairs(qhwowv) do
545 print(string.format("%d: %s", v.freq, v.tag))
548 for k, v in pairs(qhv) do
549 print(string.format("%d: %s", v.freq, v.tag))
552 for k, v in pairs(wowv) do
553 print(string.format("%d: %s", v.freq, v.tag))
555 elseif not FileUtil.fileExists("../static.lua") then
556 print("../static.lua doesn't exist; you can't skip the compile step.")
561 print("Creating: Icons.tga")
562 --if not FileUtil.fileExists("../Art/Icons.tga") then
563 -- print("You'll need to manually create Art/Icons.tga, ImageMagick's SVG support seems to have been broken recently.")
565 FileUtil.convertImage("Data/art.svg", "../Art/Icons.tga")
568 local version_string = "UNKNOWN"
570 if archive_zip or archive_7z then
571 FileUtil.unlinkDirectory("QuestHelper")
573 FileUtil.createDirectory("QuestHelper")
574 FileUtil.createDirectory("QuestHelper/Art")
575 FileUtil.createDirectory("QuestHelper/Fonts")
579 -- Try to coax git to give us something to use for the version.
580 local stream = io.popen("git describe --tags HEAD", "r")
582 info = stream:read("*line")
584 -- Replace the first dash with a dot.
585 info = string.gsub(info, "^(.*)%-(.*)%-g(.*)$", "%1%.%2%-%3", 1)
591 -- Didn't get a nice looking tag to use, try to at least get a hash for the version.
592 stream = io.popen("git-log -1 --pretty=\"format:%2-%h\"", "r")
594 info = stream:read("*line")
600 -- We have no idea what version this is, will use the string already in the TOC.
604 FileUtil.copyFile("../QuestHelper.toc", "QuestHelper", "^(##%s-Version%s-):%s*(.*)%s*$",
606 version_string = string.gsub(info, "%%2", b) return a..": "..version_string
609 local file_version_pattern = "%1"..string.format("%q", version_string)
611 FileUtil.forEachFile("..", function (file)
612 local ext = FileUtil.extension(file)
613 if ext == "lua" or ext == "xml" then
615 FileUtil.copyFile(file, "QuestHelper")
617 FileUtil.copyFile(file, "QuestHelper",
618 "(assert%s*%b())", "--[[ %1 ]]",
619 "(QuestHelper:Assert%s*%b())", "--[[ %1 ]]",
620 "(QuestHelper_File%[[^%]]*%]%s*=%s*)\"[^\"]+\"", file_version_pattern)
625 FileUtil.forEachFile("../Art", function (file)
626 local ext = FileUtil.extension(file)
627 if ext == "blp" or ext == "tga" then
628 FileUtil.copyFile(file, "QuestHelper/Art")
632 FileUtil.forEachFile("../Fonts", function (file)
633 local ext = FileUtil.extension(file)
634 if ext == "txt" or ext == "ttf" then
635 FileUtil.copyFile(file, "QuestHelper/Fonts")
639 for i, dir in ipairs({"AstrolabeQH", "ChatThrottleLib", "LibAboutPanel", "lang"}) do
640 FileUtil.copyDirectoryRecursively("../"..dir, "QuestHelper/"..dir)
643 FileUtil.copyFile("../MinimapArrow.tga", "QuestHelper")
644 FileUtil.copyFile("../arrow_image.blp", "QuestHelper")
645 FileUtil.copyFile("../arrow_image_down.blp", "QuestHelper")
646 FileUtil.copyFile("../triangle.tga", "QuestHelper")
647 FileUtil.copyFile("../line.tga", "QuestHelper")
650 local archive = "../QuestHelper-"..version_string..".zip"
651 print("Creating "..archive)
652 FileUtil.unlinkFile(archive)
653 FileUtil.createZipArchive("QuestHelper", archive)
657 local archive = "../QuestHelper-"..version_string..".7z"
658 print("Creating "..archive)
659 FileUtil.unlinkFile(archive)
660 FileUtil.create7zArchive("QuestHelper", archive)
663 FileUtil.unlinkDirectory("QuestHelper")
669 --local outfile = io.open("profile.txt", "w+")
670 --profiler:report(outfile)