Saturday, April 5, 2014

Buildapp Minutia

Before I get to telling you what I've been up to lately, let me share some insight into what it takes to build Lisp executables on Windows and Linux. This is something I had to do recently at work. I wouldn't normally bother, but we still have Windows dev boxes, and it seemed like an interesting enough exercise. In no particular order, here are things you need to think about when you're putting things together with buildapp.

Be careful with your storage folders

My usual tactic for this sort of thing is to store static files and stored files in the project directory. It's easy to do because I just get to use relative paths everywhere, and it works well enough if your distribution plan is basically git clone https://github.com/you/whatever.git. The only downside is that you have to run the program in the context of its project directory, or have random static folders pop up in random places.

For people who don't just randomly write Lisp code for fun, this isn't exactly acceptable. To do proper binary deployment for a more general audience, you need to make sure that there's a single consistent place for your program to store stuff. And if you want to be cross-platform about it, you can't just do (defvar *storage* "~/project-name/") and call it a day. Fortunately, user-homedir-pathname is a thing, so what I ended up doing is setting my storage directory to (merge-pathnames ".project-name/" (user-homedir-pathname)). However, it wasn't quite that simple.

My other usual tactic is to just put such config variable declarations in the appropriate package.lisp file, and leave it at that. Unfortunately, when I tried that with buildapp, the first teammate who tried it responded with

Why is this complaining that "C:\Users\Inaimathi\2dmacs\" doesn't exist?

The problem is that buildapp takes your image and binary and ships it, including anything you did at compile or config time. What I really wanted was for my binary to check at runtime what the users' home directory is and use that as the starting point for its own storage directory. The way I ended up doing that, though I'm sure others are possible, was to define the appropriate variables where I'd usually put them, then set them again in the entry-point function. Mine happened to be called main, because why not, so what you'd see if you looked into the appropriate files is[1]

;;; package.lisp

...

(defparameter *storage* nil)

...
;;; start.lisp

...

(defun main (&optional argv)
  ...
  (setf *storage* (merge-pathnames ".2dmacs/" (user-homedir-pathname)))
  ...
  )

That lets the appropriate functions access storage files, but makes sure that *storage* will ultimately be set to the appropriate directory on the users' machine rather than yours.

Native SBCL shell sucks balls under Windows

We had a couple problems deploying a specific part of the system that dealt with running external programs. And like the title says, that was balls on Windows. Not that I expected it to run perfectly across operating systems, but it turned out to be particularly thorny. First off, external-program doesn't do what we wanted. It kinda does, but doesn't seem to let you have a hook in to search the users' environment variables for program locations. Luckily, we're using SBCL, and its native sb-ext:run-program does give you this facility in the form of the search keyword argument. Also, for some reason SBCL handles streams really oddly in Windows? Not sure why, but the end result was that I couldn't capture *standard-output* in the way I wanted to in one part of the code. We ended up dealing with that by calling sb-ext:run-program with :output :stream, then collecting the result through sb-ext:process-output instead of direct stream capture. Cool, I guess. It seemed to work in testing. But the particular shell command we were using started seizing up once we hit a particular corpus size[2]. The solution we ended up taking was using uiop:run-program instead. It deals with streams the way I was expecting and handles all shell commands we've thrown at it so far very snappily.

Finding dependencies is ... non-trivial

buildapp doesn't load dependencies automatically. It expects you to pass it a bunch of --load-system command line arguments for things you want included in the final binary. The first thing I did was check the docs. There didn't seem to be a dependency-tree call anywhere, so I wrote this one. On a whim though, I dropped by #lisp, where I got the opportunity to ask Zach about it[3]. It turns out that what I wrote is really only going to get you the dependencies declared in asdf files, and there are apparently enough people out there who hook manual load or ql:quickload statements in through odd, ad-hoc ways that he doesn't go that route. Turns out that what he does is asdf:load-system/ql:quickload the thing he wants to build, then runs ql:write-asdf-manifest-file to find the list of all systems included in the image after everything relevant has been loaded. The buildapp call you make after that needs to look something like buildapp --manifest-file wherever/you/had/ql:write-manifest-file/output.txt --load-system foo --load-system bar ... --entry your-entry-fn --output your-program-name. Mildly annoying, but at least it'll get you what you need.


Footnotes

1 - [back] - Ok, it's mildly more complicated than this too, because I wanted to deal with some command line arguments, and I wanted some static files to be optionally re-generated at each program load. You can still see the basic principle.

2 - [back] - If you must know, it was git ls-tree, and the call started hanging around the time that we committed the ~third file into the target repository.

3 - [back] - Here's the transcript, for those of you who want the details:

13:58 <inaimathi> Anyone here who knows things about asdf
                  dependencies, and is willing to answer
                  questions/lend an eye?
13:58 <Xach> inaimathi: I know a bit. What's up?
13:59 <inaimathi> I'm trying to find the dependency tree
                  (preferably ordered) of a given asdf system.
13:59 <inaimathi> Is there a build-in way of doing that?
13:59 <inaimathi> *built
13:59 <inaimathi> Hm
14:00 <inaimathi> Actually, you might be able to help with the
                  larger problem too. The real problem is that I'm
                  trying to run buildapp for a project, and I want
                  to know what systems I need to load as part of
                  the shell command.
14:00 <Xach> oh. the way i do that is to load the project once with
             dependencies downloaded automatically and then note
             what was loaded.
14:00 <Xach> then i load it again with just those things.
14:01 <Xach> it is not great but arbitrary things load during
             find-system time so i'm not sure if there's a nice way
             around it.
14:01 <inaimathi> How do I go about doing that?
14:02 <inaimathi> That is, loading a project while getting output
                  of what's being loaded. Is there a ql flag or
                  something?
14:03 <Xach> inaimathi: i don't actually note the specifics. i just
             load it, and then have quicklisp dump out an index to
             the currently installed libraries via
             (ql:write-asdf-manifest-file
             "/path/to/my/project/system-index.txt")
14:03 <inaimathi> Ah
14:03 <Xach> then i use buildapp --asdf-manifest system-index.txt
             --<the rest of the stuff>
14:04 <Xach> Each time I make a new project I refine the makefile
             technique a little more
14:05 <inaimathi> Ok then. Do you think something like
                  http://stackoverflow.com/a/22732580/190887 could
                  be a valid approach, or is that going to miss
                  things?
14:07 <Xach> inaimathi: One difficulty arises from .asd files with
             things like (eval-when ... (asdf:load-system
             "some-prerequisite"))
14:07 <inaimathi> Right; those wouldn't be noted by the asdf system
                  itself.
14:07 <inaimathi> Dammit.
14:08 <Xach> I also don't know if the slot you're looking at
             includes :defsystem-depends-on dependencies.
14:08 <Xach> inaimathi: I once asked about how to do this on
             asdf-devel and I got an answer I didn't really
             understand (it was complicated) and I haven't
             revisited it. And I'm not sure there's an archive you
             can search for it.
14:09 <rtoym> asdf-devel is on gmane.org
14:10 <inaimathi> Hm. I'll take the asdf-manifest-file approach for
                  the moment, and probably ask around on asdf-devel
                  later.
14:10 <inaimathi> Thanks!

1 comment:

  1. โบนัส โปรโมชั่น รวมทั้งสิทธิพิเศษที่คาสิโนมอบให้ คุณลักษณะในเกมสล็อตJOKER123 ยกตัวอย่างเช่น รอบโบนัสและก็ฟรีสปิน สามารถทำสิ่งอัศจรรย์สำหรับบัญชีธนาคารของคุณ แม้คุณโชคดีพอเพียงคุณสามารถรับดูผู้เล่นสล็อตบนแพลตฟอร์มสตรีมมิ่งต่างๆที่เล่นแบบเรียลไทม์! สิ่งนี้มีคุณประโยชน์สำหรับในการศึกษาขั้นตอนการทำงานของสล็อตออนไลน์ และก็บางทีอาจถือเอากลเม็ดสองสามข้อไปพร้อม คุณสามารถค้นหาสตรีมเมอร์มากไม่น้อยเลยทีเดียวเพื่อมองได้ฟรีบน Youtube หรือ ะดูด้านด้านล่าง: แนวทางชนะที่สล็อต – สัมภาษณ์ผู้เล่นสล็อตแมชชีนมืออาชีพเขาเริ่มยังไงเขาเล่นเครื่องอะไรผู้คนสามารถหาเครื่องสล็อตซึ่งสามารถหาผลประโยชน์ได้จากที่ไหนจะต้องใช้เงินเท่าไรก็เลยจะบรรลุเป้าหมายและก็อีกเยอะมาก!มีสิ่งดังที่ได้กล่าวมาแล้วเป็น “ยุทธวิธีสล็อต” หรือเปล่าสล็อตออนไลน์น่าสะพรึงกลัวน้อยกว่ามากมายเมื่อเทียบกับเกมบนโต๊ะ ไม่มีอุบายจำนวนมากที่เกี่ยว ถ้าหากคุณอยากชนะแจ็คพอตสล็อตแมชชีน คุณจำต้องพนันด้วยเงินนิดหน่อย แล้วก็จำนวนมากแล้ว หวังว่าจะดีเยี่ยมที่สุด แต่ มีไม่กี่ขั้นตอนที่คุณสามารถทำเป็นก่อนจะกระทำพนันด้วยเงินจริงที่บางทีอาจเพิ่มจังหวะของคุณ ทางออกที่ดีเยี่ยมที่สุดของคุณเป็นการดู RPT และก็ความปั่นป่วนของเกม สิ่งพวกนี้ควรจะกำหนดเอาไว้ภายในคาสิโนออนไลน์โดยตรง แม้กระนั้น แม้คุณไม่เจอมันตรงนั้น คุณสามารถค้นหาข้อมูลนี้ได้จากการค้นหาเกมโดย Google อย่างง่าย เกมสล็อตดำเนินการเช่นไรสล็อตมีรูปร่างรวมทั้งแบบที่ต่างกัน แม้กระนั้นเบื้องต้นสำหรับในการเล่นเกมยังคงดังเดิม วางจำนวนเงินพนันของคุณต่อการหมุน คุณสามารถตั้งค่าเล่นอัตโนมัติได้ทุกแห่งระหว่าง 10 ถึง 100 สปินในแต่ละครั้ง เมื่อวงล้อหยุดหมุน คุณจะมองเห็นการรวมกันของเครื่องหมายบนหนึ่งเพย์ไลน์หรือมากยิ่งกว่า คุณอาจได้รับรางวัลจากเครื่องวัดเครดิตของสล็อตJOKER123 ดังนี้ขึ้นกับชุดค่าผสม เงินรางวัลของคุณจะเข้าสู่บัญชีของคุณอัตโนมัติเวลาที่คุณเล่น ขึ้นกับสล็อตที่คุณเลือก นี่บางทีอาจเป็นการเข้าสู่ไม่นิเกมโบนัสหรือสะสมฟรีสปิน เมื่อเสร็จแล้ว คุณสามารถเลือกถอนหรือออกมาจากเกมได้โดยทันที คุณจะไม่สูญเสียเงินรางวัลของคุณถ้าคุณออกมาจากเกมช่วงนี้ สล็อตออนไลน์ทุกช่องจะมีช่องชำระเงิน เครื่องหมาย แล้วก็วงล้อ อย่างไรก็ดี สล็อตจำนวนไม่ใช่น้อยถึงขนาดพิเศษและก็เพิ่มคุณลักษณะ ดังเช่น เครื่องหมายเสริม สแคทเทอร์ และก็รอบโบนัส เพื่อเกมสนุกสนานเพิ่มขึ้นสำหรับผู้ใช้มาดูกันว่าสิ่งกลุ่มนี้คืออะไร:เพย์ไลน์เส้นกลุ่มนี้ปรากฏบนจอ โดยแสดงการชำระเงินตามชุดค่าผสมที่ชนะ เกมสล็อตที่ต่างกันมีปริมาณช่องชำระเงินที่ไม่เหมือนกันซึ่งชอบเริ่มจากซ้ายไปขวาเพื่อจะชนะ ผู้เล่นจำเป็นจะต้องจับคู่เครื่องหมายตามแนวชำระเงินที่ใช้งานอยู่Wildsเครื่องหมายพวกนี้เจอได้ในเกมสล็อต สามารถใช้เพื่อสร้างชุดค่าผสมที่ชนะ Wilds มาในแบบต่างๆเช่น :ไวด์ขยาย ซึ่งครอบคลุมทั้งยังรีลเดินป่าที่เคลื่อนไปๆมาๆจากรีลหนึ่งไปอีกรีลในเวลาที่ผู้เล่นหมุนซึ่งอยู่นิ่งรวมทั้งยังคงอยู่บนวงล้อตามปริมาณการหมุนที่ระบุเกลื่อนกลาดกระจัดกระจายปกติเครื่องหมายกระจัดกระจายการลงหยุดเป็นสิ่งที่ปลดล็อกเกมโบนัสสล็อตหรือจะชำระเงินไม่น้อยเลยทีเดียว คุณจำเป็นต้องตีขั้นต่ำสองคู่บนเพย์ไลน์เดียวกัน หรือไม่ก็ปฏิบัติหน้าที่เป็นเครื่องหมายธรรมดาอื่น รอบโบนัส รอบโบนัสเป็นคุณโภคทรัพย์ที่น่าเร้าใจที่พรีเซนเทชั่นโดยสล็อตที่อนุญาตให้ผู้เล่นเพิ่มเงินรางวัลได้เป็นอย่างมาก คุณจำเป็นจะต้องวางเครื่องหมายอะไรบางอย่างบนเพย์ไลน์ (ขึ้นกับสล็อต) เพื่อปลดล็อกเกมโบนัส แล้วหลังจากนั้นชอบได้รับปริมาณการหมุนฟรีที่เลือกไว้เพื่อใช้ รอบโบนัสมีประสิทธิภาพที่จะทำให้ท่านทำเงินได้มากมายอย่างไม่ต้องสงสัย แต่ ไม่รับรับรองเพียงแต่เพราะเหตุว่าคุณไปสู่เกมโบนัส โบนัสการสมัครสมาชิกคาสิโนออนไลน์จะมอบเงินคุณฟรีเพื่อทดลองเล่นเกมสล็อตJOKER123ออนไลน์ คุณสามารถเก็บสิ่งที่คุณชนะได้ อย่างไรก็แล้วแต่ คุณ

    ReplyDelete