Plugin release creator

Post Python examples to help other users.
User avatar
Project Leader
Posts: 2286
Joined: Sat Jul 07, 2012 1:59 am

Plugin release creator

Postby satoon101 » Tue Sep 16, 2014 6:23 pm

I know BackRaw created his own, but I thought I would share my release creation script.

This script needs to be loaded on a server to work. The reason for this is that it verifies that the given plugin can at the very least load. It also requires that the plugin have a PluginInfo instance that has a 'version' and whose 'basename' is set to the plugin directory name.

This plugin also includes files in all directories relative to the plugin (data, cfg, logs, sound, events, translations) with the allowed file types for the eventual online Plugin Manager we will have.

I still need to update the script to support both sub-plugins and custom packages. For now it only works for base plugins.

Syntax: Select all

# ../

# =============================================================================
# =============================================================================
# Python Imports
# Contextlib
from contextlib import suppress
# Sys
import sys
# Zipfile
from zipfile import ZIP_DEFLATED
from zipfile import ZipFile

# Site-Package Imports
# Path
from path import Path

# Source.Python Imports
# Commands
from commands.server import ServerCommand
# Core
from core.manager import core_plugin_manager
# Paths
from paths import CFG_PATH
from paths import EVENT_PATH
from paths import GAME_PATH
from paths import LOG_PATH
from paths import PLUGIN_DATA_PATH
from paths import PLUGIN_PATH
from paths import SOUND_PATH
from paths import TRANSLATION_PATH
# Plugins
from import PluginInfo

# =============================================================================
# =============================================================================
# Set to the base path to save releases
SAVE_DIRECTORY = Path('e:/Project Releases')

# Set to the server command to register for creating a release

# =============================================================================
# =============================================================================
readable_data = [

allowed_filetypes = {
# Allow readable data and uncompiled Python files in the plugins directory
PLUGIN_PATH: readable_data + ['py'],

# Allow readable data and txt placeholders
# (for database storage) in data directory
PLUGIN_DATA_PATH: readable_data + ['txt'],

# Allow text placeholders in cfg directory
CFG_PATH: ['txt'],

# Allow text placeholders in log directory
LOG_PATH: ['txt'],

# Allow only sounds that can be played by the engine in the sound directory
SOUND_PATH: ['mp3', 'wav'],

# Allow text placeholders in the events directory
EVENT_PATH: ['txt'],

# Allow only translation ini files in the translations directory

exceptions = {
# Make sure to not include server specific translation files
TRANSLATION_PATH: ['_server.ini'],

# =============================================================================
# =============================================================================
def zip_plugin(command):
"""Main function to create a project release."""
# Was no plugin_name given?
if not command.get_arg_string():
print('No plugin given.')
print('zipit <plugin_name>')

# Is the given plugin_name invalid?
plugin_name = command.get_arg(1)
plugin_path = PLUGIN_PATH.joinpath(plugin_name)
if not plugin_path.joinpath(plugin_name + '.py').isfile():
print('Invalid plugin name "{0}"'.format(plugin_name))

# Get the plugin's instance.
# This also loads the plugin if it is not loaded.
plugin = core_plugin_manager[plugin_name]

# Did the plugin fail to load?
if plugin is None:
print('Plugin "{0}" failed to load'.format(plugin_name))

# Set a base version variable in case the version isn't found
version = None

# Loop through all loaded modules
for module_name in sys.modules:

# Is the current module not related to the given plugin_name?
if not module_name.startswith(plugin_name):

# Get the module's instance
module = sys.modules[module_name]

# Loop through all items in the module
for item in dir(module):

# Get the item's instance
instance = getattr(module, item)

# Is this instance not a PluginInfo instance?
if not isinstance(instance, PluginInfo):

# Is this PluginInfo instance not the plugin's PluginInfo instance?
# This is used in case of sub-plugins with their own PluginInfo.
if instance.basename != plugin_name:

# Get the plugin's version and break the loop
version = instance.version

# Break the loop if the version was already found
if version is not None:

# Was no version found?
if version is None:
print('No version found for "{0}"'.format(plugin_name))

# Get the directory to save the release in

# Create the release directory if it doesn't exist

# Get the release path
'{0} - v{1}.zip'.format(plugin_name, version))

# Does the release already exist?
if ZIP_PATH.isfile():
print('Release version "{0}" already exists'.format(version))

# Create the zip file
with ZipFile(ZIP_PATH, 'w', ZIP_DEFLATED) as zip_file:

# Loop through all directories to search
for PATH in allowed_filetypes:

# Loop through all files in the directory relative to the plugin
for file in find_files(
PATH.files('{0}.*'.format(plugin_name)), PATH):

# Add the file to the zip
add_file(zip_file, file, PATH)

# Loop through all files in the plugin specific directory
for file in find_files(
PATH.joinpath(plugin_name).walkfiles(), PATH):

# Add the file to the zip
add_file(zip_file, file, PATH)

'Successfully created {0} version {1} release:'.format(
plugin_name, version))

def find_files(generator, PATH):
"""Yield files that should be added to the zip."""
# Suppress FileNotFoundError in case the
# plugin specific directory does not exist.
with suppress(FileNotFoundError):

# Loop through the files from the given generator
for file in generator:

# Is the current file not allowed?
if not file.ext[1:] in allowed_filetypes[PATH]:

# Does the given directory have exceptions?
if PATH in exceptions:

# Loop through the directory's exceptions
for exception in exceptions[PATH]:

# Is this file not allowed?
if exception in

# Is the file not an exception?
yield file

# Is the file allowed?
yield file

def add_file(zip_file, file, PATH):
"""Add the given file and all parent directories to the zip."""
# Write the file to the zip
zip_file.write(file, file.replace(GAME_PATH, ''))

# Get the file's parent directory
parent = file.parent

# Get all parent directories to add to the zip
while GAME_PATH in parent:

# Is the current directory not yet included in the zip?
current = parent.replace(GAME_PATH, '').replace('\\', '/') + '/'
if not current in zip_file.namelist():

# Add the parent directory to the zip
zip_file.write(parent, current)

# Get the parent's parent
parent = parent.parent

To use it, save it as ../addons/source-python/plugins/plugin_zipper/ and use the following:

Code: Select all

sp load plugin_zipper

// Usage
// zipit <plugin_name>

zipit most_damage

You can change the SAVE_DIRECTORY to any directory you wish to save your releases to. You can also change the RELEASE_COMMAND if 'zipit' doesn't suit you.
User avatar
Senior Member
Posts: 413
Joined: Sun Jul 15, 2012 1:46 am
Location: Germany

Postby BackRaw » Tue Sep 16, 2014 6:25 pm

Very nice, I like it.
My Github repositories:


Return to “Code examples / Cookbook”

Who is online

Users browsing this forum: No registered users and 1 guest