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("%[%s*([^%s%]]+)(.-)%]", linkparse)
92 :gsub("<code>(.-)</code>", "|cff40bbff%1|r")
93 :gsub("^%s*==%s*(.-)%s*==%s*$", "|cffffff00 %1|r\n")
94 :gsub("^%*%*%s", " • ")
95 :gsub("^%*%s", "• ").."\n"
102 line = stream:read("*l")
103 if not line then break end
104 if line:find("^%s*==.*==%s*$") then
109 text = text .. (count == 1 and "" or "\n") .. totext(line)
110 elseif not line:find("^%s*$") then
111 text = text .. totext(line)
117 stream = io.open("../changes.lua", "w")
119 stream:write(("QuestHelper_ChangeLog=%q\n"):format(text))
122 print("Error writing ../changes.lua")
125 print("Error opening ../changes.txt")
131 --print("Updating Astrolabe.")
132 --FileUtil.updateSVNRepo("http://svn.esamynn.org/astrolabe/branches/wotlk_data_temp", "../Astrolabe")
133 print("Updating ChatThrottleLib.")
134 FileUtil.updateSVNRepo("svn://svn.wowace.com/wow/chatthrottlelib/mainline/trunk", "../ChatThrottleLib")
138 local function saveCache()
139 local stream = io.open("build-cache.lua", "w")
140 local buffer, prebuf = CreateBuffer(), CreateBuffer()
141 DumpVariable(buffer, prebuf, cache, "cache")
142 stream:write(DumpingComplete(buffer, prebuf))
146 local function QuestHelper_IsCorrupted(data)
147 if type(data.QuestHelper_Objectives) == "table" then for version, package in pairs(data.QuestHelper_Objectives) do
148 if AuthorizedVersion(version) and type(package) == "table" then for category, objectives in pairs(package) do
149 if type(objectives) == "table" and PreWrath(version) then for name, objective in pairs(objectives) do
150 if type(objective) == "table" and objective.pos and type(objective.pos) == "table" then for i, pos in pairs(objective.pos) do
151 if pos[1] and pos[1] >= 65 then
152 print("SOMETHING IS SERIOUSLY WRONG, found Wrath coordinates in non-Wrath files (version " .. version .. ", continent " .. pos[1] .. ")")
161 -- Download the latest copy of the translated item/npc names.
163 os.execute("wget http://smariot.hopto.org/wowdata.7z -N")
165 if FileUtil.fileExists("wowdata.7z") then
166 local hash = FileUtil.fileHash("wowdata.7z")
167 if cache.wowdata_hash ~= hash then
168 -- If the archive has changed, extract the lua file from the archive.
169 cache.wowdata_hash = hash
170 os.execute("7z e -y wowdata.7z wowdata.lua")
175 if FileUtil.fileExists("wowdata.lua") then
176 loadfile("wowdata.lua")()
179 local all_input_files, unknown_input_files = {}, {}
181 local since_last_save = 0
183 FileUtil.forEachFile("LocalInput", function (name)
184 if FileUtil.extension(name) == "lua" or
185 FileUtil.extension(name) == "bak" then
187 local hash = FileUtil.fileHash(name)
188 if cache.ignored[hash] then return end
190 if since_last_save == 100 then
191 print("Collecting garbage and saving . . .")
192 collectgarbage("collect")
196 since_last_save = since_last_save + 1
198 cache.ignored[hash] = true
200 local input = loadfile(name)
205 if not (data.QuestHelper_Locale and data.QuestHelper_Objectives) then
206 print("! "..name.." isn't a QuestHelper SavedVariables file.")
210 local upgradable_data = {}
211 setfenv(input, upgradable_data)
213 QuestHelper_UpgradeDatabase(upgradable_data)
214 if QuestHelper_IsPolluted(upgradable_data) then
215 print("!! "..name.." is polluted")
218 if QuestHelper_IsCorrupted(upgradable_data) then
219 print("!! "..name.." is corrupted")
222 if not upgradable_data.QuestHelper_UID then
223 print("!! "..name.." has no UID")
227 local tempname = os.tmpname()
228 local stream = io.open(tempname, "w")
230 print("Copying/Sorting "..name)
231 stream:write(ScanAndDumpVariable(data, nil, true) or "")
233 hash = FileUtil.fileHash(tempname)
235 local input_name = "Input/"..hash..".lua"
236 if not cache.removed[input_name] then
237 if not cache.known[input_name] then
238 FileUtil.copyFile(tempname, input_name)
239 unknown_input_files[input_name] = name
241 cache.known[input_name] = name
244 cache.removed[input_name] = name
247 print("!!! Can't get hash of "..tempname..", for "..name)
249 FileUtil.unlinkFile(tempname)
252 print("!!! "..name.." couldn't be executed.")
255 print("!!! "..name.." couldn't be loaded.")
260 collectgarbage("collect")
263 FileUtil.forEachFile("Input", function (name)
264 if cache.removed[name] then
265 print("!!! Obsolete: ", cache.removed[name].." ("..name..")")
268 if not cache.known[name] then
269 unknown_input_files[name] = unknown_input_files[name] or name
272 all_input_files[name] = cache.known[name] or unknown_input_files[name]
276 local function ProcessObjective(category, name, objective, result)
277 local istring = "obj."..category.."."..name
279 if category ~= "item" then
281 if objective.pos then for i, pos in pairs(objective.pos) do
285 result[istring..".seen"] = (result[istring..".seen"] or 0) + seen
288 if objective.vendor then
289 result[istring..".vend"] = (result[istring..".vend"] or 0) + #objective.vendor
292 if objective.drop then for monster, count in pairs(objective.drop) do
293 result[istring] = (result[istring] or 0) + count
297 local function ProcessQuest(faction, level, name, quest, result)
298 local qstring = "quest."..faction.."."..level.."."..name
299 result[qstring] = (result[qstring] or 0)+((quest.finish or quest.pos) and 1 or 0)
301 if quest.item then for item_name, data in pairs(quest.item) do
302 ProcessObjective("item", item_name, data, result)
305 if quest.alt then for _, quest2 in pairs(quest.alt) do
306 ProcessQuest(faction, level, name, quest2, result)
310 local function LoadFile(file)
311 local data = loadfile(file)
315 if not pcall(setfenv(data, loaded)) then
316 print("!!!!! oh god something is wrong "..file)
322 QuestHelper_UpgradeDatabase(loaded)
324 if loaded.QuestHelper_UID then
325 result.uid = loaded.QuestHelper_UID
326 result.time = loaded.QuestHelper_SaveDate
328 if type(loaded.QuestHelper_Quests) == "table" then for version, package in pairs(loaded.QuestHelper_Quests) do
329 if AuthorizedVersion(version) and type(package) == "table" then for faction, levels in pairs(package) do
330 if type(levels) == "table" then for level, quest_list in pairs(levels) do
331 if type(quest_list) == "table" then for name, quest in pairs(quest_list) do
332 ProcessQuest(faction, level, name, quest, result)
338 if type(loaded.QuestHelper_Objectives) == "table" then for version, package in pairs(loaded.QuestHelper_Objectives) do
339 if AuthorizedVersion(version) and type(package) == "table" then for faction, levels in pairs(package) do
340 if type(objectives) == "table" then for name, objective in pairs(objectives) do
341 ProcessObjective(category, name, objective, result)
351 local function ObsoletedBy(data1, data2)
352 if data1.uid or data2.uid then
353 return data1.loc == data2.loc and data1.uid == data2.uid and (data1.time or 0) >= (data2.time or 0)
356 for key, value in pairs(data1) do
357 local value2 = data2[key]
358 if value2 == nil or value2 < value then
368 -- This entire mess is built to find obsolete files and remove them.
369 for new_name, original_name in pairs(unknown_input_files) do
370 print("Checking: ", original_name)
371 local data = file_data[new_name]
373 data = LoadFile(new_name)
374 file_data[new_name] = data
376 cache.known[new_name] = original_name
377 checked[new_name] = true
380 local uid, last_save = data.uid, data.time
383 local existing = cache.uid[uid]
385 cache.uid[uid] = {file=new_name, save=last_save}
386 file2uid[new_name] = uid
388 if existing.save >= last_save then
389 print("!!! Obsolete: ", original_name)
390 print("!!! By: ", all_input_files[existing.file])
393 file_data[new_name] = nil
394 all_input_files[new_name] = nil
395 cache.removed[new_name] = original_name
396 cache.known[new_name] = nil
397 unknown_input_files[new_name] = nil
399 print("!!! Obsolete: ", all_input_files[existing.file])
400 print("!!! By: ", original_name)
403 os.remove(existing.file)
404 file_data[existing.file] = nil
405 file2uid[existing.file] = nil
406 file2uid[new_name] = uid
407 cache.removed[existing.file] = all_input_files[existing.file]
408 all_input_files[existing.file] = nil
409 cache.known[existing.file] = nil
410 existing.file = new_name
414 for existing_name, existing_original_name in pairs(all_input_files) do
415 if not checked[existing_name] and not file2uid[existing_name] then
416 local data2 = file_data[existing_name]
418 data2 = LoadFile(existing_name)
419 file_data[existing_name] = data2
422 if data2 and not data2.uid then
423 if ObsoletedBy(data, data2) then
424 print("!!! Obsolete: ", original_name)
425 print("!!! By: ", existing_original_name)
429 file_data[new_name] = nil
430 all_input_files[new_name] = nil
431 cache.removed[new_name] = original_name
432 cache.known[new_name] = nil
433 unknown_input_files[new_name] = nil
435 elseif ObsoletedBy(data2, data) then
436 print("!!! Obsolete: ", existing_original_name)
437 print("!!! By: ", original_name)
440 os.remove(existing_name)
441 file_data[existing_name] = nil
442 all_input_files[existing_name] = nil
443 cache.removed[existing_name] = existing_original_name
444 cache.known[existing_name] = nil
445 unknown_input_files[existing_name] = nil
454 checked, file_data = nil, nil
457 collectgarbage("collect")
459 --print("Compiling Lightheaded/eql3 data. . .")
462 local total_count = 0
464 local pairfrequencies = {}
465 for name, origin in pairs(all_input_files) do
466 total_count = total_count + 1 -- I fuckin' hate Lua
468 for name, origin in pairs(all_input_files) do
469 counter = counter + 1
470 print("Compiling " .. counter .. "/" .. total_count .. ": ", origin)
471 --CompileInputFile(name)
472 local ret, msg = pcall(CompileInputFile, name, pairfrequencies)
474 print("!!!!! FAILURE")
479 print("Processing final data")
480 local finaldata = CompileFinish()
482 local finalstatic = io.open("../static.lua", "w")
483 finalstatic:write("QuestHelper_File[\"static.lua\"] = \"Development Version\"\n")
484 finalstatic:write("QuestHelper_Loadtime[\"static.lua\"] = GetTime()\n")
485 finalstatic:write("QuestHelper_StaticData={")
487 for k, v in pairs(finaldata) do
488 collectgarbage("collect")
489 local foloc = string.format("static_%s.lua", k)
490 local finalout = "../" .. foloc
491 local tempout = finalout
492 if condense then tempout = string.format("static_%s.large", k) end
494 print("Writing: " .. tempout)
495 local stream = io.open(tempout, "w")
496 stream:write(string.format("QuestHelper_File[\"%s\"] = \"Development Version\"\n", foloc))
497 stream:write(string.format("QuestHelper_Loadtime[\"%s\"] = GetTime()\n", foloc))
498 stream:write(string.format("if GetLocale() ~= \"%s\" then return end\n", k)) -- wellp
499 stream:write(ScanAndDumpVariable(v, string.format("QuestHelper_StaticData_%s", k)))
503 print("Condensing " .. finalout)
504 assert(os.execute(string.format("lua LuaSrcDiet.lua --maximum %s -o %s", tempout, finalout)) ~= -1)
506 local data_loader = loadfile("LuaSrcDiet.lua")
507 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
508 setfenv(data_loader, data)
514 finalstatic:write(string.format(" %s=QuestHelper_StaticData_%s,", k, k))
515 --assert(FileUtil.fileContains("../QuestHelper.toc", foloc), "didn't contain " .. foloc)
518 finalstatic:write("}")
519 io.close(finalstatic)
522 local qhfrequencies = {}
523 local wowfrequencies = {}
525 for k, v in pairs(pairfrequencies) do
526 table.insert(qhwowv, {freq = v, tag = k})
527 local qh, wow = SplitVersion(k)
528 qhfrequencies[qh] = (qhfrequencies[qh] or 0) + v
529 wowfrequencies[wow] = (wowfrequencies[wow] or 0) + v
535 for k, v in pairs(qhfrequencies) do table.insert(qhv, {freq = v, tag = k}) end
536 for k, v in pairs(wowfrequencies) do table.insert(wowv, {freq = v, tag = k}) end
538 table.sort(qhwowv, function (a, b) return a.freq < b.freq end)
539 table.sort(qhv, function (a, b) return a.freq < b.freq end)
540 table.sort(wowv, function (a, b) return a.freq < b.freq end)
542 for k, v in pairs(qhwowv) do
543 print(string.format("%d: %s", v.freq, v.tag))
546 for k, v in pairs(qhv) do
547 print(string.format("%d: %s", v.freq, v.tag))
550 for k, v in pairs(wowv) do
551 print(string.format("%d: %s", v.freq, v.tag))
553 elseif not FileUtil.fileExists("../static.lua") then
554 print("../static.lua doesn't exist; you can't skip the compile step.")
559 print("Creating: Icons.tga")
560 --if not FileUtil.fileExists("../Art/Icons.tga") then
561 -- print("You'll need to manually create Art/Icons.tga, ImageMagick's SVG support seems to have been broken recently.")
563 FileUtil.convertImage("Data/art.svg", "../Art/Icons.tga")
566 local version_string = "UNKNOWN"
568 if archive_zip or archive_7z then
569 FileUtil.unlinkDirectory("QuestHelper")
571 FileUtil.createDirectory("QuestHelper")
572 FileUtil.createDirectory("QuestHelper/Art")
573 FileUtil.createDirectory("QuestHelper/Fonts")
577 -- Try to coax git to give us something to use for the version.
578 local stream = io.popen("git describe --tags HEAD", "r")
580 info = stream:read("*line")
582 -- Replace the first dash with a dot.
583 info = string.gsub(info, "^(.*)%-(.*)%-g(.*)$", "%1%.%2%-%3", 1)
589 -- Didn't get a nice looking tag to use, try to at least get a hash for the version.
590 stream = io.popen("git-log -1 --pretty=\"format:%2-%h\"", "r")
592 info = stream:read("*line")
598 -- We have no idea what version this is, will use the string already in the TOC.
602 FileUtil.copyFile("../QuestHelper.toc", "QuestHelper", "^(##%s-Version%s-):%s*(.*)%s*$",
604 version_string = string.gsub(info, "%%2", b) return a..": "..version_string
607 local file_version_pattern = "%1"..string.format("%q", version_string)
609 FileUtil.forEachFile("..", function (file)
610 local ext = FileUtil.extension(file)
611 if ext == "lua" or ext == "xml" then
613 FileUtil.copyFile(file, "QuestHelper")
615 FileUtil.copyFile(file, "QuestHelper",
616 "(assert%s*%b())", "--[[ %1 ]]",
617 "(QuestHelper:Assert%s*%b())", "--[[ %1 ]]",
618 "(QuestHelper_File%[[^%]]*%]%s*=%s*)\"[^\"]+\"", file_version_pattern)
623 FileUtil.forEachFile("../Art", function (file)
624 local ext = FileUtil.extension(file)
625 if ext == "blp" or ext == "tga" then
626 FileUtil.copyFile(file, "QuestHelper/Art")
630 FileUtil.forEachFile("../Fonts", function (file)
631 local ext = FileUtil.extension(file)
632 if ext == "txt" or ext == "ttf" then
633 FileUtil.copyFile(file, "QuestHelper/Fonts")
637 for i, dir in ipairs({"AstrolabeQH", "ChatThrottleLib", "LibAboutPanel", "lang"}) do
638 FileUtil.copyDirectoryRecursively("../"..dir, "QuestHelper/"..dir)
642 local archive = "../QuestHelper-"..version_string..".zip"
643 print("Creating "..archive)
644 FileUtil.unlinkFile(archive)
645 FileUtil.createZipArchive("QuestHelper", archive)
649 local archive = "../QuestHelper-"..version_string..".7z"
650 print("Creating "..archive)
651 FileUtil.unlinkFile(archive)
652 FileUtil.create7zArchive("QuestHelper", archive)
655 FileUtil.unlinkDirectory("QuestHelper")
661 --local outfile = io.open("profile.txt", "w+")
662 --profiler:report(outfile)