Card Stories - Packaging and Distribution Documentation
From Farsides Wiki
A python package, a Debian GNU/Linux package and a twistd plugin are bundled together. The server side tests and client side tests can be run with a single command.
Contents |
[edit] Packaging
[edit] setup.py
The setup.py exists in conformance to the distutils python requirements. The reference documentation for each fields complements the guide. It is best understood by reading an example such as the django setup.py.
The setup.py file alone describes the binary distribution created with
but it must be complemented with a MANIFEST.in file as explained in the source distribution documentation.
There is no support to run tests at this time.
[edit] Debian GNU/Linux
A native package exists, meaning the debian package is part of the source distribution and is maintained by the author of the package. The static files, including the JavaScript client are copied to /usr/share/cardstories. The database is stored in /var/cache/cardstories. The daemon is launched by an init file using the twistd plugin architecture.
The presence of the setup.py file at the root of the source tree is automatically detected by debhelper 7 and used to figure out the layout of the Debian package.
[edit] Building
cd cardstories
v=1.0.3 ; python setup.py sdist --dist-dir .. ; mv ../cardstories-$v.tar.gz ../cardstories_$v.orig.tar.gz
debuild -uc -us
[edit] twistd plugin
The plugin is installed in /etc/cardstories and its integration can be seen with :
...
cardstories Find out a card using a sentence made up by another
player
...
If it needs to be run manually, the usage can be displayed with:
Usage: twistd [options] cardstories [-h|--help] [-p|--port=] [-s|--ssl-port=] [-P|--ssl-pem=] [-d|--db=] [-v|--verbose]
Options:
-v, --verbose verbosity level
-i, --interface= Interface to which the server must be bound [default:
127.0.0.1]
-p, --port= Port on which to listen [default: 4923]
-s, --ssl-port= Port on which to listen for SSL
-P, --ssl-pem= certificate path name [default: /etc/cardstories/cert.pem]
-d, --db= sqlite3 game database path [default:
/var/cache/cardstories/cardstories.sqlite]
-a, --auth= authentication plugin values : basic
--auth-db= sqlite3 auth database path [default:
/var/cache/cardstories/auth.sqlite]
--loop= Number of ping batchs to run, -1 means forever, 0 means
never [default: -1]
--static= directory where /static files will be fetched [default:
/usr/share/cardstories]
--version
--help Display this help and exit.
Find out a card using a sentence made up by another player
[edit] Tests
The maintain.mk file at the root of the source tree runs the tests with
make -C tests check
python-coverage -e
PYTHONPATH=.. python-coverage -x test_auth.py
__main__.AuthTestInit.test00_create ... [OK]
__main__.AuthTest.test00 ... [OK]
-------------------------------------------------------------------------------
Ran 2 tests in 0.326s
PASSED (successes=2)
PYTHONPATH=.. python-coverage -x test_service.py
__main__.CardstoriesServiceTestInit.test00_startService ... [OK]
__main__.CardstoriesServiceTest.test01_create ... [OK]
__main__.CardstoriesServiceTest.test02_participate ... [OK]
__main__.CardstoriesServiceTest.test03_player2game ... [OK]
__main__.CardstoriesServiceTest.test04_pick ... [OK]
__main__.CardstoriesServiceTest.test05_state_vote ... [OK]
__main__.CardstoriesServiceTest.test06_vote ... [OK]
__main__.CardstoriesServiceTest.test07_complete ... [OK]
__main__.CardstoriesServiceTest.test08_game ... [OK]
__main__.CardstoriesServiceTest.test09_invitation ... [OK]
__main__.CardstoriesServiceTest.test10_lobby ... [OK]
__main__.CardstoriesServiceTestHandle.test01_required ... [OK]
__main__.CardstoriesServiceTestHandle.test02_handle ... [OK]
__main__.CardstoriesServiceTestRun.test00_run ... [OK]
-------------------------------------------------------------------------------
Ran 14 tests in 2.926s
PASSED (successes=14)
PYTHONPATH=.. python-coverage -x test_site.py
__main__.CardstoriesSiteTest.test00_render ... [OK]
__main__.CardstoriesSiteTest.test00_render_static ... [OK]
__main__.CardstoriesSiteTest.test01_wrap_http ... [OK]
__main__.CardstoriesSiteTest.test02_wrap_http_fail ... [OK]
__main__.CardstoriesSiteTest.test03_handle ... [OK]
__main__.AGPLResourceTest.test00_render ... [OK]
__main__.AGPLResourceTest.test01_agpl ... [OK]
-------------------------------------------------------------------------------
Ran 7 tests in 0.050s
PASSED (successes=7)
PYTHONPATH=.. python-coverage -x test_tap.py
__main__.CardstoriesServerTest.test01_connect ... [OK]
__main__.CardstoriesServerTest.test02_parseOptions ... [OK]
__main__.CardstoriesServerTestSSL.test01 ... [OK]
-------------------------------------------------------------------------------
Ran 3 tests in 0.153s
PASSED (successes=3)
python-coverage -m -a -r ../cardstories/*.py
Name Stmts Exec Cover Missing
-------------------------------------------------------
../cardstories/__init__ 1 1 100%
../cardstories/auth 38 38 100%
../cardstories/service 248 240 96% 112-114, 183, 186, 252, 330, 334
../cardstories/site 71 67 94% 36, 61, 64, 108
../cardstories/tap 21 21 100%
-------------------------------------------------------
TOTAL 379 367 96%
The client side tests are run from a web browser and should display at the bottom of the page:
[edit] Automated AGPL compliance
A webservice partially automating the publication of server side sources licensed under the AGPL is included with the cardstories game. It could be reused in different contexts to help the occasional hacker to comply with the terms of the AGPL.
The primary reason for an author to use the AGPL instead of the GPL is to display a link to the server sources, as explained in the preamble:
…if your program is a web application, its interface could display a “Source” link that leads users to an archive of the code. There are many ways you could offer source, and different solutions will be better for different programs;
When deploying an AGPL application server side, care must be taken to provide a way for the user to download the corresponding sources, including the local modifications:
Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software.
If the software is a webservice serving static files such as cardstories, the original sources could be included in an archive available at the same location ( for instance http://cardstories.dachary.org/cardstories/static/cardstories.zip ) and a link added in each page visible to the user.
[edit] Republishing server sources
If someone implements a quick but useful hack in the code, the overhead of the compliance is comparatively high. For a few lines of patch in a python file, one would need to rebuild a source distribution. For cardstories It could be done as follows on Debian GNU/Linux :
git init
git add .
git commit -m 'archive modified version'
apt-get install --reinstall python-cardstories
git diff > /tmp/cardstories-hack.diff
cd /tmp
apt-get source python-cardstories
cd python-cardstories-*/cardstories
patch -R < /tmp/cardstories-hack.diff
apt-get build-dep python-cardstories
dpkg -i ../*cardstories*.deb
It is a fairly long sequence of commands and it requires a knowledge of Debian GNU/Linux packaging that may not be available, even to a seasoned python hacker.
[edit] Automated source archive creation
A path in the webservice builds an archive containing the server side sources from the installed files instead of using the source files from the original distribution. A first version is implemented in cardstories to collect the files that would not be visible if browsing the static tree served by the webservice, i.e.
- site.py
- service.py
- tap.py
The code triggered by the /agpl path is as follows:
def __init__(self, directory, module):
self.directory = directory
self.module = module
self.zipfile = module.__name__ + '.zip'
static.File.__init__(self, self.directory + '/' + self.zipfile)
def render(self, request):
self.update()
return static.File.render(self, request)
def update(self):
directory = os.path.dirname(self.module.__file__)
archive = self.directory + '/' + self.zipfile
f = zipfile.ZipFile(archive, 'w')
for path in glob.glob(directory + '/*.py'):
f.write(path, os.path.basename(path))
f.close()
This automation only addresses a specific case of AGPL compliance though. When more complex modifications are made to the software or its dependencies, there is a need for a careful update of the complete and corresponding source.