Merge remote-tracking branch 'origin/development'

This commit is contained in:
blitzman
2017-02-07 19:00:10 -05:00
22 changed files with 584 additions and 573 deletions

View File

@@ -1,8 +1,8 @@
version = "0.2.3"
tag = "git"
def test():
import tests.runTests
import unittest
unittest.main(defaultTest="discover", testLoader=tests.runTests.loader)
version = "0.2.3"
tag = "git"
def test():
import tests.runTests
import unittest
unittest.main(defaultTest="discover", testLoader=tests.runTests.loader)

View File

@@ -1,30 +1,30 @@
# ===============================================================================
# Copyright (C) 2010 Diego Duclos
#
# This file is part of eos.
#
# eos is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# eos is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with eos. If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================
from sqlalchemy import Column, Table, String
from sqlalchemy.orm import mapper
from eos.db import gamedata_meta
from eos.types import MetaData
metadata_table = Table("metadata", gamedata_meta,
Column("field_name", String, primary_key=True),
Column("field_value", String))
mapper(MetaData, metadata_table)
# ===============================================================================
# Copyright (C) 2010 Diego Duclos
#
# This file is part of eos.
#
# eos is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# eos is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with eos. If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================
from sqlalchemy import Column, Table, String
from sqlalchemy.orm import mapper
from eos.db import gamedata_meta
from eos.types import MetaData
metadata_table = Table("metadata", gamedata_meta,
Column("field_name", String, primary_key=True),
Column("field_value", String))
mapper(MetaData, metadata_table)

View File

@@ -1,13 +1,13 @@
# modifyActiveArmorResonancePostPercent
#
# Used by:
# Modules from group: Armor Hardener (156 of 156)
# Modules from group: Flex Armor Hardener (4 of 4)
type = "active"
def handler(fit, module, context):
for damageType in ("kinetic", "thermal", "explosive", "em"):
fit.ship.boostItemAttr("armor%sDamageResonance" % damageType.capitalize(),
module.getModifiedItemAttr("%sDamageResistanceBonus" % damageType),
stackingPenalties=True)
# modifyActiveArmorResonancePostPercent
#
# Used by:
# Modules from group: Armor Hardener (156 of 156)
# Modules from group: Flex Armor Hardener (4 of 4)
type = "active"
def handler(fit, module, context):
for damageType in ("kinetic", "thermal", "explosive", "em"):
fit.ship.boostItemAttr("armor%sDamageResonance" % damageType.capitalize(),
module.getModifiedItemAttr("%sDamageResistanceBonus" % damageType),
stackingPenalties=True)

View File

@@ -1,13 +1,13 @@
# modifyActiveShieldResonancePostPercent
#
# Used by:
# Modules from group: Flex Shield Hardener (5 of 5)
# Modules from group: Shield Hardener (97 of 97)
type = "active"
def handler(fit, module, context):
for damageType in ("kinetic", "thermal", "explosive", "em"):
fit.ship.boostItemAttr("shield" + damageType.capitalize() + "DamageResonance",
module.getModifiedItemAttr(damageType + "DamageResistanceBonus"),
stackingPenalties=True)
# modifyActiveShieldResonancePostPercent
#
# Used by:
# Modules from group: Flex Shield Hardener (5 of 5)
# Modules from group: Shield Hardener (97 of 97)
type = "active"
def handler(fit, module, context):
for damageType in ("kinetic", "thermal", "explosive", "em"):
fit.ship.boostItemAttr("shield" + damageType.capitalize() + "DamageResonance",
module.getModifiedItemAttr(damageType + "DamageResistanceBonus"),
stackingPenalties=True)

View File

@@ -1,10 +1,10 @@
# shipBonusRepairSystemsBonusATC2
#
# Used by:
# Ship: Vangel
type = "passive"
def handler(fit, ship, context):
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Repair Systems"),
"armorDamageAmount", ship.getModifiedItemAttr("shipBonusATC2"))
# shipBonusRepairSystemsBonusATC2
#
# Used by:
# Ship: Vangel
type = "passive"
def handler(fit, ship, context):
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Repair Systems"),
"armorDamageAmount", ship.getModifiedItemAttr("shipBonusATC2"))

View File

@@ -1,11 +1,11 @@
# shipEnergyNeutralizerTransferAmountBonusAB
#
# Used by:
# Ship: Bhaalgorn
type = "passive"
def handler(fit, ship, context):
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Energy Neutralizer",
"energyNeutralizerAmount", ship.getModifiedItemAttr("shipBonusAB"),
skill="Amarr Battleship")
# shipEnergyNeutralizerTransferAmountBonusAB
#
# Used by:
# Ship: Bhaalgorn
type = "passive"
def handler(fit, ship, context):
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Energy Neutralizer",
"energyNeutralizerAmount", ship.getModifiedItemAttr("shipBonusAB"),
skill="Amarr Battleship")

View File

@@ -1,13 +1,13 @@
# skillReactiveArmorHardenerCapNeedBonus
#
# Used by:
# Skill: Resistance Phasing
type = "passive"
def handler(fit, src, context):
lvl = src.level
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Armor Resistance Shift Hardener", "capacitorNeed",
src.getModifiedItemAttr("capNeedBonus") * lvl)
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Resistance Phasing"), "capacitorNeed",
src.getModifiedItemAttr("capNeedBonus") * lvl)
# skillReactiveArmorHardenerCapNeedBonus
#
# Used by:
# Skill: Resistance Phasing
type = "passive"
def handler(fit, src, context):
lvl = src.level
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Armor Resistance Shift Hardener", "capacitorNeed",
src.getModifiedItemAttr("capNeedBonus") * lvl)
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Resistance Phasing"), "capacitorNeed",
src.getModifiedItemAttr("capNeedBonus") * lvl)

View File

@@ -1,13 +1,13 @@
# skillReactiveArmorHardenerDurationBonus
#
# Used by:
# Skill: Resistance Phasing
type = "passive"
def handler(fit, src, context):
lvl = src.level
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Armor Resistance Shift Hardener", "duration",
src.getModifiedItemAttr("durationBonus") * lvl)
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Resistance Phasing"), "duration",
src.getModifiedItemAttr("durationBonus") * lvl)
# skillReactiveArmorHardenerDurationBonus
#
# Used by:
# Skill: Resistance Phasing
type = "passive"
def handler(fit, src, context):
lvl = src.level
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Armor Resistance Shift Hardener", "duration",
src.getModifiedItemAttr("durationBonus") * lvl)
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Resistance Phasing"), "duration",
src.getModifiedItemAttr("durationBonus") * lvl)

View File

@@ -1,11 +1,11 @@
# subsystemBonusAmarrDefensiveArmorRepairAmount
#
# Used by:
# Subsystem: Legion Defensive - Nanobot Injector
type = "passive"
def handler(fit, module, context):
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Repair Systems"),
"armorDamageAmount", module.getModifiedItemAttr("subsystemBonusAmarrDefensive"),
skill="Amarr Defensive Systems")
# subsystemBonusAmarrDefensiveArmorRepairAmount
#
# Used by:
# Subsystem: Legion Defensive - Nanobot Injector
type = "passive"
def handler(fit, module, context):
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Repair Systems"),
"armorDamageAmount", module.getModifiedItemAttr("subsystemBonusAmarrDefensive"),
skill="Amarr Defensive Systems")

View File

@@ -1,11 +1,11 @@
# subsystemBonusGallenteDefensiveArmorRepairAmount
#
# Used by:
# Subsystem: Proteus Defensive - Nanobot Injector
type = "passive"
def handler(fit, module, context):
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Repair Systems"),
"armorDamageAmount", module.getModifiedItemAttr("subsystemBonusGallenteDefensive"),
skill="Gallente Defensive Systems")
# subsystemBonusGallenteDefensiveArmorRepairAmount
#
# Used by:
# Subsystem: Proteus Defensive - Nanobot Injector
type = "passive"
def handler(fit, module, context):
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Repair Systems"),
"armorDamageAmount", module.getModifiedItemAttr("subsystemBonusGallenteDefensive"),
skill="Gallente Defensive Systems")

View File

@@ -1,23 +1,23 @@
class Enum():
def __init__(self):
pass
@classmethod
def getTypes(cls):
for stuff in cls.__dict__:
if stuff.upper() == stuff:
yield stuff
@classmethod
def getName(cls, v):
map = getattr(cls, "_map", None)
if map is None:
map = cls._map = {}
for type in cls.getTypes():
map[cls.getValue(type)] = type
return map.get(v)
@classmethod
def getValue(cls, type):
return cls.__dict__[type]
class Enum():
def __init__(self):
pass
@classmethod
def getTypes(cls):
for stuff in cls.__dict__:
if stuff.upper() == stuff:
yield stuff
@classmethod
def getName(cls, v):
map = getattr(cls, "_map", None)
if map is None:
map = cls._map = {}
for type in cls.getTypes():
map[cls.getValue(type)] = type
return map.get(v)
@classmethod
def getValue(cls, type):
return cls.__dict__[type]

View File

@@ -1,29 +1,29 @@
# ===============================================================================
# Copyright (C) 2010 Diego Duclos
#
# This file is part of eos.
#
# eos is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# eos is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with eos. If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================
class EqBase(object):
def __eq__(self, other):
return type(self) == type(other) and self.ID == other.ID
def __ne__(self, other):
return type(self) != type(other) or self.ID != other.ID
def __hash__(self):
return id(type(self)) + self.ID
# ===============================================================================
# Copyright (C) 2010 Diego Duclos
#
# This file is part of eos.
#
# eos is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# eos is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with eos. If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================
class EqBase(object):
def __eq__(self, other):
return type(self) == type(other) and self.ID == other.ID
def __ne__(self, other):
return type(self) != type(other) or self.ID != other.ID
def __hash__(self):
return id(type(self)) + self.ID

View File

@@ -1,118 +1,118 @@
# ===============================================================================
# Copyright (C) 2010 Diego Duclos
#
# This file is part of eos.
#
# eos is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# eos is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with eos. If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================
import itertools
class Graph(object):
def __init__(self, fit, function, data=None):
self.fit = fit
self.data = {}
if data is not None:
for name, d in data.iteritems():
self.setData(Data(name, d))
self.function = function
def clearData(self):
self.data.clear()
def setData(self, data):
self.data[data.name] = data
def getIterator(self):
pointNames = []
pointIterators = []
for data in self.data.itervalues():
pointNames.append(data.name)
pointIterators.append(data)
return self._iterator(pointNames, pointIterators)
def _iterator(self, pointNames, pointIterators):
for pointValues in itertools.product(*pointIterators):
point = {}
for i in xrange(len(pointValues)):
point[pointNames[i]] = pointValues[i]
yield point, self.function(point)
class Data(object):
def __init__(self, name, dataString, step=None):
self.name = name
self.step = step
self.data = self.parseString(dataString)
def parseString(self, dataString):
if not isinstance(dataString, basestring):
return (Constant(dataString),)
dataList = []
for data in dataString.split(";"):
if isinstance(data, basestring) and "-" in data:
# Dealing with a range
dataList.append(Range(data, self.step))
else:
dataList.append(Constant(data))
return dataList
def __iter__(self):
for data in self.data:
for value in data:
yield value
def isConstant(self):
return len(self.data) == 1 and self.data[0].isConstant()
class Constant(object):
def __init__(self, const):
if isinstance(const, basestring):
self.value = None if const == "" else float(const)
else:
self.value = const
def __iter__(self):
yield self.value
def isConstant(self):
return True
class Range(object):
def __init__(self, string, step):
start, end = string.split("-")
self.start = float(start)
self.end = float(end)
self.step = step
def __iter__(self):
current = start = self.start
end = self.end
step = self.step or (end - start) / 50.0
i = 1
while current < end:
current = start + i * step
i += 1
yield current
def isConstant(self):
return False
# ===============================================================================
# Copyright (C) 2010 Diego Duclos
#
# This file is part of eos.
#
# eos is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# eos is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with eos. If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================
import itertools
class Graph(object):
def __init__(self, fit, function, data=None):
self.fit = fit
self.data = {}
if data is not None:
for name, d in data.iteritems():
self.setData(Data(name, d))
self.function = function
def clearData(self):
self.data.clear()
def setData(self, data):
self.data[data.name] = data
def getIterator(self):
pointNames = []
pointIterators = []
for data in self.data.itervalues():
pointNames.append(data.name)
pointIterators.append(data)
return self._iterator(pointNames, pointIterators)
def _iterator(self, pointNames, pointIterators):
for pointValues in itertools.product(*pointIterators):
point = {}
for i in xrange(len(pointValues)):
point[pointNames[i]] = pointValues[i]
yield point, self.function(point)
class Data(object):
def __init__(self, name, dataString, step=None):
self.name = name
self.step = step
self.data = self.parseString(dataString)
def parseString(self, dataString):
if not isinstance(dataString, basestring):
return (Constant(dataString),)
dataList = []
for data in dataString.split(";"):
if isinstance(data, basestring) and "-" in data:
# Dealing with a range
dataList.append(Range(data, self.step))
else:
dataList.append(Constant(data))
return dataList
def __iter__(self):
for data in self.data:
for value in data:
yield value
def isConstant(self):
return len(self.data) == 1 and self.data[0].isConstant()
class Constant(object):
def __init__(self, const):
if isinstance(const, basestring):
self.value = None if const == "" else float(const)
else:
self.value = const
def __iter__(self):
yield self.value
def isConstant(self):
return True
class Range(object):
def __init__(self, string, step):
start, end = string.split("-")
self.start = float(start)
self.end = float(end)
self.step = step
def __iter__(self):
current = start = self.start
end = self.end
step = self.step or (end - start) / 50.0
i = 1
while current < end:
current = start + i * step
i += 1
yield current
def isConstant(self):
return False

Binary file not shown.

View File

@@ -1 +1 @@
__all__ = ["fitDps"]
__all__ = ["fitDps"]

View File

@@ -1,2 +1,2 @@
__all__ = ["ammo", "ammoIcon", "attributeDisplay", "baseIcon", "baseName",
"capacitorUse", "maxRange", "price", "propertyDisplay", "state", "misc", "abilities"]
__all__ = ["ammo", "ammoIcon", "attributeDisplay", "baseIcon", "baseName",
"capacitorUse", "maxRange", "price", "propertyDisplay", "state", "misc", "abilities"]

Binary file not shown.

View File

@@ -19,6 +19,7 @@
import os
import logging
import imp
import wx
@@ -30,15 +31,15 @@ from gui.graph import Graph
from gui.bitmapLoader import BitmapLoader
from config import parsePath
# Don't actually import the thing, since it takes for fucking ever
try:
import matplotlib as mpl
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as Canvas
from matplotlib.figure import Figure
imp.find_module('matplotlib')
enabled = True
mplImported = False
except ImportError:
enabled = False
mplImported = False
logger = logging.getLogger(__name__)
if not enabled:
logger.info("Problems importing matplotlib; continuing without graphs")
@@ -50,6 +51,15 @@ class GraphFrame(wx.Frame):
global enabled
global mplImported
# Import here
try:
import matplotlib as mpl
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as Canvas
from matplotlib.figure import Figure
enabled = True
except ImportError:
enabled = False
self.legendFix = False
if not enabled:
return

View File

@@ -146,6 +146,8 @@ class MainFrame(wx.Frame):
# Load stored settings (width/height/maximized..)
self.LoadMainFrameAttribs()
self.disableOverrideEditor = disableOverrideEditor
# Fix for msw (have the frame background color match panel color
if 'wxMSW' in wx.PlatformInfo:
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
@@ -205,7 +207,7 @@ class MainFrame(wx.Frame):
self.closePageId = wx.NewId()
self.widgetInspectMenuID = wx.NewId()
self.SetMenuBar(MainMenuBar())
self.SetMenuBar(MainMenuBar(self))
self.registerMenu()
# Internal vars to keep track of other windows (graphing/stats)

View File

@@ -21,8 +21,7 @@ import wx
import config
from service.character import Character
import gui.mainFrame
import gui.graphFrame
#import gui.graphFrame
import gui.globalEvents as GE
from gui.bitmapLoader import BitmapLoader
@@ -32,7 +31,7 @@ if 'wxMac' not in wx.PlatformInfo or ('wxMac' in wx.PlatformInfo and wx.VERSION
class MainMenuBar(wx.MenuBar):
def __init__(self):
def __init__(self, mainFrame):
self.characterEditorId = wx.NewId()
self.damagePatternEditorId = wx.NewId()
self.targetResistsEditorId = wx.NewId()
@@ -58,7 +57,7 @@ class MainMenuBar(wx.MenuBar):
wx.ID_COPY = wx.NewId()
wx.ID_PASTE = wx.NewId()
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.mainFrame = mainFrame
wx.MenuBar.__init__(self)
# File menu
@@ -139,7 +138,7 @@ class MainMenuBar(wx.MenuBar):
self.Enable(self.eveFittingsId, False)
self.Enable(self.exportToEveId, False)
if not gui.mainFrame.disableOverrideEditor:
if not self.mainFrame.disableOverrideEditor:
windowMenu.AppendSeparator()
attrItem = wx.MenuItem(windowMenu, self.attrEditorId, "Attribute Overrides\tCTRL+B")
attrItem.SetBitmap(BitmapLoader.getBitmap("fit_rename_small", "gui"))

View File

@@ -1,257 +1,257 @@
#!/usr/bin/env python
"""
Script for generating distributables based on platform skeletons.
User supplies path for pyfa code base, root skeleton directory, and where the
builds go. The builds are automatically named depending on the pyfa config
values of `version` and `tag`. If it's a Stable release, the naming
convention is:
pyfa-pyfaversion-expansion-expversion-platform
If it is not Stable (tag=git), we determine if the pyfa code base includes
the git repo to use as an ID. If not, uses randomly generated 6-character ID.
The unstable naming convention:
pyfa-YYYMMDD-id-platform
dist.py can also build the Windows installer provided that it has a path to
Inno Setup (and, for generating on non-Windows platforms, that WINE is
installed). To build the EXE file, `win` must be included in the platforms to
be built.
"""
#@todo: ensure build directory can be written to
# todo: default build and dist directories
from optparse import OptionParser
import os.path
import shutil
import sys
import tarfile
import datetime
import random
import string
import zipfile
import errno
from subprocess import call
class FileStub():
def write(self, *args):
pass
def flush(self, *args):
pass
i = 0
def loginfo(path, names):
# Print out a "progress" and return directories / files to ignore
global i
i += 1
if i % 10 == 0:
sys.stdout.write(".")
sys.stdout.flush()
return ()
def copyanything(src, dst):
try:
shutil.copytree(src, dst, ignore=loginfo)
except: # python >2.5
try:
shutil.copy(src, dst)
except:
raise
def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
return ''.join(random.choice(chars) for x in range(size))
def zipdir(path, zip):
for root, dirs, files in os.walk(path):
for file in files:
zip.write(os.path.join(root, file))
skels = ['win', 'src', 'mac', 'mac-deprecated']
iscc = "C:\Program Files (x86)\Inno Setup 5\ISCC.exe" # inno script location via wine
if __name__ == "__main__":
oldstd = sys.stdout
parser = OptionParser()
parser.add_option("-s", "--skeleton", dest="skeleton", help="Location of Pyfa-skel directory")
parser.add_option("-b", "--base", dest="base", help="Location of cleaned read-only base directory")
parser.add_option("-d", "--destination", dest="destination", help="Where to copy our distributable")
parser.add_option("-p", "--platforms", dest="platforms", help="Comma-separated list of platforms to build", default=','.join(skels))
parser.add_option("-q", "--quiet", dest="silent", action="store_true")
parser.add_option("-w", "--winexe", dest="winexe", action="store_true", help="Build the Windows installer file (needs Inno Setup). Must include 'win' in platform options")
parser.add_option("-z", "--zip", dest="zip", action="store_true", help="zip archive instead of tar")
options, args = parser.parse_args()
if options.skeleton is None or options.base is None or options.destination is None:
print "Need --skeleton argument as well as --base and --destination argument"
parser.print_help()
sys.exit()
if options.silent:
sys.stdout = FileStub()
options.platforms = options.platforms.split(",")
for skel in skels:
if skel not in options.platforms:
continue
print "\n======== %s ========"%skel
info = {}
config = {}
setup = {}
skeleton = os.path.expanduser(os.path.join(options.skeleton, skel))
execfile(os.path.join(options.base, "config.py"), config)
execfile(os.path.join(skeleton, "info.py"), info)
execfile(os.path.join(options.base, "setup.py"), setup)
destination = os.path.expanduser(options.destination)
if not os.path.isdir(destination) or not os.access(destination, os.W_OK | os.X_OK):
print "Destination directory does not exist or is not writable: {}".format(destination)
sys.exit()
dirName = info["arcname"]
nowdt = datetime.datetime.now()
now = "%04d%02d%02d" % (nowdt.year, nowdt.month, nowdt.day)
git = False
if config['tag'].lower() == "git":
try: # if there is a git repo associated with base, use master commit
with open(os.path.join(options.base, ".git", "refs", "heads", "master"), 'r') as f:
id = f.readline()[0:6]
git = True
except: # else, use custom ID
id = id_generator()
fileName = "pyfa-{}-{}-{}".format(now, id, info["os"])
else:
fileName = "pyfa-{}-{}-{}-{}".format(
config['version'],
config['expansionName'].lower(),
config['expansionVersion'],
info["os"]
)
archiveName = "{}.{}".format(fileName, "zip" if options.zip else "tar.bz2")
tmpDir = os.path.join(os.getcwd(), dirName) # tmp directory where files are copied
tmpFile = os.path.join(os.getcwd(), archiveName)
try:
print "Copying skeleton to ", tmpDir
shutil.copytree(skeleton, tmpDir, ignore=loginfo)
print
source = os.path.expanduser(options.base)
root = os.path.join(tmpDir, info["base"])
# it is easier to work from the source directory
oldcwd = os.getcwd()
os.chdir(source)
if info["library"]:
print "Injecting files into", info["library"]
libraryFile = os.path.join(root, info["library"])
with zipfile.ZipFile(libraryFile, 'a') as library:
for dir in setup['packages']:
zipdir(dir, library)
library.write('pyfa.py', 'pyfa__main__.py')
library.write('config.py')
else: # platforms where we don't have a packaged library
print "Copying modules into", root
for dir in setup['packages']:
copyanything(dir, os.path.join(root, dir))
# add some additional files to root dir for these platforms
# (hopefully can figure out a way later for OS X to use the one in
# it's library)
if skel == 'mac':
setup['include_files'] += ['pyfa.py']
if skel in ('src', 'mac-deprecated'):
setup['include_files'] += ['pyfa.py', 'config.py']
print
print "Copying included files:",
for file in setup['include_files']:
if isinstance(file, basestring):
print file,
copyanything(file, os.path.join(root, file))
print
print "Creating images zipfile:",
os.chdir('imgs')
imagesFile = os.path.join(root, "imgs.zip")
with zipfile.ZipFile(imagesFile, 'w') as images:
for dir in setup['icon_dirs']:
print dir,
zipdir(dir, images)
os.chdir(oldcwd)
print
print "Creating archive"
if options.zip:
archive = zipfile.ZipFile(tmpFile, 'w', compression=zipfile.ZIP_DEFLATED)
zipdir(dirName, archive)
archive.close()
else:
archive = tarfile.open(tmpFile, "w:bz2")
archive.add(tmpDir, arcname=info["arcname"])
archive.close()
print "Moving archive to ", destination
shutil.move(tmpFile, destination)
if "win" in skel and options.winexe:
print "Compiling EXE"
if config['tag'].lower() == "git":
if git: # if git repo info available, use git commit
expansion = "git-%s"%(id)
else: # if there is no git repo, use timestamp
expansion = now
else: # if code is Stable, use expansion name
expansion = "%s %s"%(config['expansionName'], config['expansionVersion']),
calllist = ["wine"] if 'win' not in sys.platform else []
call(calllist + [
iscc,
"pyfa-setup.iss",
"/dMyAppVersion=%s"%(config['version']),
"/dMyAppExpansion=%s"%(expansion),
"/dMyAppDir=pyfa",
"/dMyOutputDir=%s"%destination,
"/dMyOutputFile=%s"%fileName]) #stdout=devnull, stderr=devnull
print "EXE completed"
except Exception as e:
print "Encountered an error: \n\t", e
raise
finally:
print "Deleting tmp files\n"
try:
shutil.rmtree("dist") # Inno dir
except:
pass
try:
shutil.rmtree(tmpDir)
except:
pass
try:
os.unlink(tmpFile)
except:
pass
sys.stdout = oldstd
if os.path.isdir(destination):
print os.path.join(destination, os.path.split(tmpFile)[1])
else:
print destination
#!/usr/bin/env python
"""
Script for generating distributables based on platform skeletons.
User supplies path for pyfa code base, root skeleton directory, and where the
builds go. The builds are automatically named depending on the pyfa config
values of `version` and `tag`. If it's a Stable release, the naming
convention is:
pyfa-pyfaversion-expansion-expversion-platform
If it is not Stable (tag=git), we determine if the pyfa code base includes
the git repo to use as an ID. If not, uses randomly generated 6-character ID.
The unstable naming convention:
pyfa-YYYMMDD-id-platform
dist.py can also build the Windows installer provided that it has a path to
Inno Setup (and, for generating on non-Windows platforms, that WINE is
installed). To build the EXE file, `win` must be included in the platforms to
be built.
"""
#@todo: ensure build directory can be written to
# todo: default build and dist directories
from optparse import OptionParser
import os.path
import shutil
import sys
import tarfile
import datetime
import random
import string
import zipfile
import errno
from subprocess import call
class FileStub():
def write(self, *args):
pass
def flush(self, *args):
pass
i = 0
def loginfo(path, names):
# Print out a "progress" and return directories / files to ignore
global i
i += 1
if i % 10 == 0:
sys.stdout.write(".")
sys.stdout.flush()
return ()
def copyanything(src, dst):
try:
shutil.copytree(src, dst, ignore=loginfo)
except: # python >2.5
try:
shutil.copy(src, dst)
except:
raise
def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
return ''.join(random.choice(chars) for x in range(size))
def zipdir(path, zip):
for root, dirs, files in os.walk(path):
for file in files:
zip.write(os.path.join(root, file))
skels = ['win', 'src', 'mac', 'mac-deprecated']
iscc = "C:\Program Files (x86)\Inno Setup 5\ISCC.exe" # inno script location via wine
if __name__ == "__main__":
oldstd = sys.stdout
parser = OptionParser()
parser.add_option("-s", "--skeleton", dest="skeleton", help="Location of Pyfa-skel directory")
parser.add_option("-b", "--base", dest="base", help="Location of cleaned read-only base directory")
parser.add_option("-d", "--destination", dest="destination", help="Where to copy our distributable")
parser.add_option("-p", "--platforms", dest="platforms", help="Comma-separated list of platforms to build", default=','.join(skels))
parser.add_option("-q", "--quiet", dest="silent", action="store_true")
parser.add_option("-w", "--winexe", dest="winexe", action="store_true", help="Build the Windows installer file (needs Inno Setup). Must include 'win' in platform options")
parser.add_option("-z", "--zip", dest="zip", action="store_true", help="zip archive instead of tar")
options, args = parser.parse_args()
if options.skeleton is None or options.base is None or options.destination is None:
print "Need --skeleton argument as well as --base and --destination argument"
parser.print_help()
sys.exit()
if options.silent:
sys.stdout = FileStub()
options.platforms = options.platforms.split(",")
for skel in skels:
if skel not in options.platforms:
continue
print "\n======== %s ========"%skel
info = {}
config = {}
setup = {}
skeleton = os.path.expanduser(os.path.join(options.skeleton, skel))
execfile(os.path.join(options.base, "config.py"), config)
execfile(os.path.join(skeleton, "info.py"), info)
execfile(os.path.join(options.base, "setup.py"), setup)
destination = os.path.expanduser(options.destination)
if not os.path.isdir(destination) or not os.access(destination, os.W_OK | os.X_OK):
print "Destination directory does not exist or is not writable: {}".format(destination)
sys.exit()
dirName = info["arcname"]
nowdt = datetime.datetime.now()
now = "%04d%02d%02d" % (nowdt.year, nowdt.month, nowdt.day)
git = False
if config['tag'].lower() == "git":
try: # if there is a git repo associated with base, use master commit
with open(os.path.join(options.base, ".git", "refs", "heads", "master"), 'r') as f:
id = f.readline()[0:6]
git = True
except: # else, use custom ID
id = id_generator()
fileName = "pyfa-{}-{}-{}".format(now, id, info["os"])
else:
fileName = "pyfa-{}-{}-{}-{}".format(
config['version'],
config['expansionName'].lower(),
config['expansionVersion'],
info["os"]
)
archiveName = "{}.{}".format(fileName, "zip" if options.zip else "tar.bz2")
tmpDir = os.path.join(os.getcwd(), dirName) # tmp directory where files are copied
tmpFile = os.path.join(os.getcwd(), archiveName)
try:
print "Copying skeleton to ", tmpDir
shutil.copytree(skeleton, tmpDir, ignore=loginfo)
print
source = os.path.expanduser(options.base)
root = os.path.join(tmpDir, info["base"])
# it is easier to work from the source directory
oldcwd = os.getcwd()
os.chdir(source)
if info["library"]:
print "Injecting files into", info["library"]
libraryFile = os.path.join(root, info["library"])
with zipfile.ZipFile(libraryFile, 'a') as library:
for dir in setup['packages']:
zipdir(dir, library)
library.write('pyfa.py', 'pyfa__main__.py')
library.write('config.py')
else: # platforms where we don't have a packaged library
print "Copying modules into", root
for dir in setup['packages']:
copyanything(dir, os.path.join(root, dir))
# add some additional files to root dir for these platforms
# (hopefully can figure out a way later for OS X to use the one in
# it's library)
if skel == 'mac':
setup['include_files'] += ['pyfa.py']
if skel in ('src', 'mac-deprecated'):
setup['include_files'] += ['pyfa.py', 'config.py']
print
print "Copying included files:",
for file in setup['include_files']:
if isinstance(file, basestring):
print file,
copyanything(file, os.path.join(root, file))
print
print "Creating images zipfile:",
os.chdir('imgs')
imagesFile = os.path.join(root, "imgs.zip")
with zipfile.ZipFile(imagesFile, 'w') as images:
for dir in setup['icon_dirs']:
print dir,
zipdir(dir, images)
os.chdir(oldcwd)
print
print "Creating archive"
if options.zip:
archive = zipfile.ZipFile(tmpFile, 'w', compression=zipfile.ZIP_DEFLATED)
zipdir(dirName, archive)
archive.close()
else:
archive = tarfile.open(tmpFile, "w:bz2")
archive.add(tmpDir, arcname=info["arcname"])
archive.close()
print "Moving archive to ", destination
shutil.move(tmpFile, destination)
if "win" in skel and options.winexe:
print "Compiling EXE"
if config['tag'].lower() == "git":
if git: # if git repo info available, use git commit
expansion = "git-%s"%(id)
else: # if there is no git repo, use timestamp
expansion = now
else: # if code is Stable, use expansion name
expansion = "%s %s"%(config['expansionName'], config['expansionVersion']),
calllist = ["wine"] if 'win' not in sys.platform else []
call(calllist + [
iscc,
"pyfa-setup.iss",
"/dMyAppVersion=%s"%(config['version']),
"/dMyAppExpansion=%s"%(expansion),
"/dMyAppDir=pyfa",
"/dMyOutputDir=%s"%destination,
"/dMyOutputFile=%s"%fileName]) #stdout=devnull, stderr=devnull
print "EXE completed"
except Exception as e:
print "Encountered an error: \n\t", e
raise
finally:
print "Deleting tmp files\n"
try:
shutil.rmtree("dist") # Inno dir
except:
pass
try:
shutil.rmtree(tmpDir)
except:
pass
try:
os.unlink(tmpFile)
except:
pass
sys.stdout = oldstd
if os.path.isdir(destination):
print os.path.join(destination, os.path.split(tmpFile)[1])
else:
print destination