Skip to content

Commit

Permalink
encode platform info into all file/directory names
Browse files Browse the repository at this point in the history
  • Loading branch information
rfk committed Nov 7, 2009
1 parent e740bff commit 4f9e899
Show file tree
Hide file tree
Showing 11 changed files with 131 additions and 76 deletions.
14 changes: 7 additions & 7 deletions README.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ like this:

prog.exe - esky bootstrapping executable
updates/ - work area for fetching/unpacking updates
appname-X.Y/ - specific version of the application
appname-X.Y.platform/ - specific version of the application
prog.exe - executable(s) as produced by bbfreeze
library.zip - pure-python modules frozen by bbfreeze
pythonXY.dll - python DLL
Expand All @@ -42,12 +42,12 @@ distutils setup.py file.

To upgrade to a new version "appname-X.Z", esky performs the following steps:
* extract it into a temporary directory under "updates"
* move all bootstrapping files into "appname-X.Z/esky-bootstrap"
* atomically rename it into the main directory as "appname-X.Z"
* move the contents of "appname-X.Z/esky-bootstrap" into the main dir
* remove the "appname-X.Z/esky-bootstrap" directory
* remove files not in "appname-X.Z/esky-bootstrap.txt" from the main dir
* remove the "appname-X.Y" directory
* move all bootstrapping files into "appname-X.Z.platform/esky-bootstrap"
* atomically rename it into the main directory as "appname-X.Z.platform"
* move contents of "appname-X.Z.platform/esky-bootstrap" into the main dir
* remove the "appname-X.Z.platform/esky-bootstrap" directory
* remove files not in "appname-X.Z.platform/esky-bootstrap.txt"
* remove the "appname-X.Y.platform" directory

Where such facilities are provided by the operating system, this process is
performed within a filesystem transaction. Neverthless, the esky bootstrapping
Expand Down
1 change: 1 addition & 0 deletions TODO.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

* Bundle package_data and data_files into the distribution somehow
* better matching of python dlls (e.g. don't match "pythoncom26.dll")

28 changes: 15 additions & 13 deletions esky/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
prog.exe - esky bootstrapping executable
updates/ - work area for fetching/unpacking updates
appname-X.Y/ - specific version of the application
appname-X.Y.platform/ - specific version of the application
prog.exe - executable(s) as produced by bbfreeze
library.zip - pure-python modules frozen by bbfreeze
pythonXY.dll - python DLL
Expand All @@ -44,12 +44,12 @@
To upgrade to a new version "appname-X.Z", esky performs the following steps:
* extract it into a temporary directory under "updates"
* move all bootstrapping files into "appname-X.Z/esky-bootstrap"
* atomically rename it into the main directory as "appname-X.Z"
* move the contents of "appname-X.Z/esky-bootstrap" into the main dir
* remove the "appname-X.Z/esky-bootstrap" directory
* remove files not in "appname-X.Z/esky-bootstrap.txt" from the main dir
* remove the "appname-X.Y" directory
* move all bootstrapping files into "appname-X.Z.platform/esky-bootstrap"
* atomically rename it into the main directory as "appname-X.Z.platform"
* move contents of "appname-X.Z.platform/esky-bootstrap" into the main dir
* remove the "appname-X.Z.platform/esky-bootstrap" directory
* remove files not in "appname-X.Z.platform/esky-bootstrap.txt"
* remove the "appname-X.Y.platform" directory
Where such facilities are provided by the operating system, this process is
performed within a filesystem transaction. Neverthless, the esky bootstrapping
Expand Down Expand Up @@ -86,6 +86,7 @@
from esky.bootstrap import split_app_version, parse_version, get_best_version
from esky.fstransact import FSTransaction
from esky.finder import SimpleVersionFinder
from esky.util import is_core_dependency


class Esky(object):
Expand Down Expand Up @@ -118,10 +119,11 @@ def __init__(self,appdir,version_finder):
self._lock_count = 0
workdir = os.path.join(appdir,"updates")
if isinstance(version_finder,basestring):
self.version_finder = SimpleVersionFinder(appname=self.name,workdir=workdir,download_url=version_finder)
self.version_finder = SimpleVersionFinder(appname=self.name,platform=self.platform,workdir=workdir,download_url=version_finder)
else:
self.version_finder = version_finder
self.version_finder.appname = self.name
self.version_finder.platform = self.platform
self.version_finder.workdir = workdir

def reinitialize(self):
Expand All @@ -134,7 +136,7 @@ def reinitialize(self):
best_version = get_best_version(self.appdir)
if best_version is None:
raise EskyBrokenError("no frozen versions found")
self.name,self.version = split_app_version(best_version)
self.name,self.version,self.platform = split_app_version(best_version)

def lock(self,num_retries=0):
"""Lock the application directory for exclusive write access.
Expand Down Expand Up @@ -256,7 +258,7 @@ def install_update(self,version):
before proceeding.
"""
# Extract update then rename into position in main app directory
target = os.path.join(self.appdir,"%s-%s"%(self.name,version))
target = os.path.join(self.appdir,"%s-%s.%s"%(self.name,version,self.platform,))
if not os.path.exists(target):
if not self.version_finder.has_version(version):
self.version_finder.fetch_version(version)
Expand All @@ -274,11 +276,11 @@ def install_update(self,version):
trn.move(os.path.join(bootstrap,"library.zip"),
os.path.join(self.appdir,"library.zip"))
for nm in os.listdir(bootstrap):
if nm.startswith("python"):
if is_core_dependency(nm):
trn.move(os.path.join(bootstrap,nm),
os.path.join(self.appdir,nm))
for nm in os.listdir(bootstrap):
if not nm.startswith("python") and nm != "library.zip":
if not is_core_dependency(nm) and nm != "library.zip":
trn.move(os.path.join(bootstrap,nm),
os.path.join(self.appdir,nm))
# Remove the bootstrap dir; the new version is now active
Expand All @@ -293,7 +295,7 @@ def install_update(self,version):
# Remove/disable the old version.
# On win32 we can't remove in-use files, so just clobber
# library.zip and leave to rest to a cleanup() call.
oldv = "%s-%s" % (self.name,self.version,)
oldv = "%s-%s.%s" % (self.name,self.version,self.platform)
oldv = os.path.join(self.appdir,oldv)
if sys.platform == "win32":
trn.remove(os.path.join(oldv,"library.zip"))
Expand Down
35 changes: 25 additions & 10 deletions esky/bdist_esky.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
named with the application name, version and platform.
The resulting zipfile is conveniently in the format expected by the default
SimpleVersionFinder.
SimpleVersionFinder. It will be named "appname-version.platform.zip"
"""

Expand All @@ -26,11 +26,11 @@

import distutils.command
from distutils.core import Command
from distutils.util import get_platform

import bbfreeze

import esky.bootstrap
from esky.util import get_platform, is_core_dependency


class bdist_esky(Command):
Expand All @@ -40,6 +40,8 @@ class bdist_esky(Command):
user_options = [
('dist-dir=', 'd',
"directory to put final built distributions in"),
('bootstrap-module=', None,
"module to use for bootstrapping esky apps"),
('includes=', None,
"list of modules to specifically include"),
('excludes=', None,
Expand All @@ -50,13 +52,16 @@ def initialize_options(self):
self.dist_dir = None
self.includes = []
self.excludes = []
self.bootstrap_module = None

def finalize_options(self):
self.set_undefined_options('bdist',('dist_dir', 'dist_dir'))

def run(self):
bsdir = os.path.join(self.dist_dir,"%s.%s"%(self.distribution.get_fullname(),get_platform(),))
fdir = os.path.join(bsdir,self.distribution.get_fullname())
fullname = self.distribution.get_fullname()
platform = get_platform()
bsdir = os.path.join(self.dist_dir,"%s.%s"%(fullname,platform,))
fdir = os.path.join(bsdir,"%s.%s"%(fullname,platform,))
if os.path.exists(bsdir):
shutil.rmtree(bsdir)
os.makedirs(fdir)
Expand All @@ -69,12 +74,22 @@ def run(self):
f.addScript(s,gui_only=s.endswith(".pyw"))
f()
# Create the bootstrap environment
bscode_source = inspect.getsource(esky.bootstrap)
bscode = imp.get_magic() + struct.pack("<i",0)
bscode += marshal.dumps(compile(bscode_source,"__main__.py","exec"))
bslib_path = os.path.join(bsdir,"library.zip")
bslib = zipfile.PyZipFile(bslib_path,"w",zipfile.ZIP_STORED)
bslib.writestr(zipfile.ZipInfo("__main__.pyc",(2000,1,1,0,0,0)),bscode)
# store the bootstrapping module
code_source = inspect.getsource(esky.bootstrap)
code = imp.get_magic() + struct.pack("<i",0)
code += marshal.dumps(compile(code_source,"bootstrap.py","exec"))
bslib.writestr(zipfile.ZipInfo("bootstrap.pyc",(2000,1,1,0,0,0)),code)
# and the main module which uses it
if self.bootstrap_module is None:
code_source = "from bootstrap import bootstrap\nbootstrap()"
else:
bsmodule = __import__(self.bootstrap_module)
code_source = inspect.getsource(bsmodule)
code = imp.get_magic() + struct.pack("<i",0)
code += marshal.dumps(compile(code_source,"__main__.py","exec"))
bslib.writestr(zipfile.ZipInfo("__main__.pyc",(2000,1,1,0,0,0)),code)
bslib.close()
manifest = ["library.zip"]
if self.distribution.has_scripts():
Expand All @@ -87,7 +102,7 @@ def run(self):
shutil.copy2(os.path.join(fdir,nm),os.path.join(bsdir,nm))
manifest.append(nm)
for nm in os.listdir(fdir):
if nm.startswith("python"):
if is_core_dependency(nm):
shutil.copy2(os.path.join(fdir,nm),os.path.join(bsdir,nm))
manifest.append(nm)
f_manifest = open(os.path.join(fdir,"esky-bootstrap.txt"),"wt")
Expand All @@ -96,7 +111,7 @@ def run(self):
f_manifest.write("\n")
f_manifest.close()
# Zip up the distribution
zfname = os.path.join(self.dist_dir,"%s.%s.zip"%(self.distribution.get_fullname(),get_platform()))
zfname = os.path.join(self.dist_dir,"%s.%s.zip"%(fullname,platform,))
zf = zipfile.ZipFile(zfname,"w")
for (dirpath,dirnames,filenames) in os.walk(bsdir):
for fn in filenames:
Expand Down
47 changes: 24 additions & 23 deletions esky/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@
update could result in the boostrapper from a new version being forced
to load an old version.
The code from this module becomes the __main__ module in the bootstrapping
environment created by esky. At application load time, it is executed with
module name "__builtin__".
The code from this module is included in the bootstrapping environment under
the module name "bootstrap".
If I can be bothered doing all this in C, I plan to eventually replace this
module with a custom program loader. For now, this lets us get up and running
Expand All @@ -31,24 +30,26 @@
# platform-specific modules and fudge the rest.
if "posix" in sys.builtin_module_names:
from posix import listdir, stat, unlink, rename, execv
def pathjoin(*args):
"""Local re-implementation of os.path.join."""
return "/".join(args)
def basename(p):
"""Local re-implementation of os.path.basename."""
return p.split("/")[-1]
SEP = "/"
elif "nt" in sys.builtin_module_names:
from nt import listdir, stat, unlink, rename, spawnv, P_WAIT
def pathjoin(*args):
"""Local re-implementation of os.path.join."""
return "\\".join(args)
def basename(p):
"""Local re-implementation of os.path.basename."""
return p.split("\\")[-1]
SEP = "\\"
else:
raise RuntimeError("unsupported platform: " + sys.platform)


def pathjoin(*args):
"""Local re-implementation of os.path.join."""
return SEP.join(args)

def basename(p):
"""Local re-implementation of os.path.basename."""
return p.split(SEP)[-1]

def dirname(p):
"""Local re-implementation of os.path.dirname."""
return SEP.join(p.split(SEP)[:-1])

def exists(path):
"""Local re-implementation of os.path.exists."""
try:
Expand Down Expand Up @@ -104,7 +105,7 @@ def get_best_version(appdir):
# To be a version directory, it must contain a "library.zip".
candidates = []
for nm in listdir(appdir):
(_,ver) = split_app_version(nm)
(_,ver,_) = split_app_version(nm)
if ver and exists(pathjoin(appdir,nm,"library.zip")):
ver = parse_version(ver)
candidates.append((ver,nm))
Expand All @@ -125,9 +126,9 @@ def get_best_version(appdir):


def split_app_version(s):
"""Split a app version string to name and version components.
"""Split a app version string to name, version and platform components.
For example, appname-0.1.2 => ("appname","0.1.2")
For example, app-name-0.1.2.win32 => ("app-name","0.1.2","win32")
"""
bits = s.split("-")
idx = 1
Expand All @@ -136,7 +137,11 @@ def split_app_version(s):
if not bits[idx][0].isalpha() or not bits[idx].isalnum():
break
idx += 1
return ("-".join(bits[:idx]),"-".join(bits[idx:]))
appname = "-".join(bits[:idx])
bits = "-".join(bits[idx:]).split(".")
version = ".".join(bits[:-1])
platform = bits[-1]
return (appname,version,platform)


def parse_version(s):
Expand Down Expand Up @@ -202,7 +207,3 @@ def _split_version_components(s):
yield s[start:end]
start = end


if __name__ == "__builtin__":
bootstrap()

18 changes: 9 additions & 9 deletions esky/finder.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@
import shutil
from urlparse import urljoin

from distutils.util import get_platform

from esky.bootstrap import parse_version
from esky.errors import *
from esky.util import extract_zipfile
Expand All @@ -30,10 +28,11 @@ class VersionFinder(object):
"""Base VersionFinder class.
This class defines the interface expected of a VersionFinder object.
It will be given two properties at initialisation time:
It will be given three properties at initialisation time:
appname: name of the application being managed
workdir: directory in which updates can be downloaded, extracted, etc
appname: name of the application being managed
platform: platform for which the application was produced
workdir: directory in which updates can be downloaded, extracted, etc
The important methods expected from any VersionFinder are:
Expand All @@ -52,8 +51,9 @@ class VersionFinder(object):
"""

def __init__(self,appname=None,workdir=None):
def __init__(self,appname=None,platform=None,workdir=None):
self.appname = appname
self.platform = platform
self.workdir = workdir

def cleanup(self):
Expand Down Expand Up @@ -116,7 +116,7 @@ def open_url(self,url):

def find_versions(self):
downloads = self.open_url(self.download_url).read()
link_re = "href=['\"](?P<href>(.*/)?%s-(?P<version>[a-zA-Z0-9\\.-]+).%s.zip)['\"]" % (self.appname,get_platform(),)
link_re = "href=['\"](?P<href>(.*/)?%s-(?P<version>[a-zA-Z0-9\\.-]+).%s.zip)['\"]" % (self.appname,self.platform,)
found = []
for match in re.finditer(link_re,downloads):
self.version_urls[match.group("version")] = match.group("href")
Expand Down Expand Up @@ -146,14 +146,14 @@ def fetch_version(self,version):
os.rename(outfilenm,self._download_name(version))

def _download_name(self,version):
return os.path.join(self.workdir,"downloads","%s-%s.%s.zip" % (self.appname,version,get_platform(),))
return os.path.join(self.workdir,"downloads","%s-%s.%s.zip" % (self.appname,version,self.platform,))

def has_version(self,version):
return os.path.exists(self._download_name(version))

def prepare_version(self,version):
dlpath = self._download_name(version)
vdir = "%s-%s" % (self.appname,version,)
vdir = "%s-%s.%s" % (self.appname,version,self.platform)
uppath = os.path.join(self.workdir,"unpack")
# Anything in the root of the zipfile is part of the boostrap
# env, so it gets placed in a special directory.
Expand Down
3 changes: 2 additions & 1 deletion esky/tests/eskytester/script1.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import os
import sys
import esky
import esky.util

if sys.platform == "win32":
dotexe = ".exe"
Expand All @@ -28,7 +29,7 @@

assert os.path.isfile(os.path.join(app.appdir,"script1"+dotexe))
assert os.path.isfile(os.path.join(app.appdir,"script2"+dotexe))
assert os.path.isdir(os.path.join(app.appdir,"eskytester-0.2"))
assert os.path.isdir(os.path.join(app.appdir,"eskytester-0.2."+esky.util.get_platform()))
script2 = os.path.join(app.appdir,"script2"+dotexe)
os.execv(script2,[script2])

Loading

0 comments on commit 4f9e899

Please sign in to comment.