1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
|
"""
PC-BASIC make.common
Python, Windows, MacOS and Linux packaging utilities
(c) 2015--2023 Rob Hagemans
This file is released under the GNU GPL version 3 or later.
"""
import sys
import os
import shutil
import glob
import json
import datetime
from subprocess import check_output, CalledProcessError
from contextlib import contextmanager
from PIL import Image
from pcbasic import NAME, VERSION, AUTHOR, COPYRIGHT
from pcbasic.data import ICON
from docs import make_htmldoc, make_usage, make_man
# paths
# root location
HERE = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
BUILD_PATH = os.path.join(HERE, 'build')
RESOURCE_PATH = os.path.join(BUILD_PATH, 'resources')
DOC_PATH = os.path.join(BUILD_PATH, 'doc')
USAGE_PATH = os.path.join(HERE, 'pcbasic', 'data')
ICON_PATTERN = os.path.join(RESOURCE_PATH, 'pcbasic.{format}')
STAMP_FILE = os.path.join(HERE, 'pcbasic', 'basic', 'data', 'release.json')
USAGE_NAME = 'USAGE.txt'
MAN_NAME = 'pcbasic.1.gz'
HTMLDOC_NAME = 'PC-BASIC_documentation.html'
###############################################################################
# make targets and components
def make_clean():
"""clean the workspace of build files; leave in-place compiled files"""
# remove traces of egg
for path in glob.glob(os.path.join(HERE, '*.egg-info')):
prune(path)
# remove intermediate builds
prune(BUILD_PATH)
# remove bytecode files
for root, dirs, files in os.walk(HERE):
for name in dirs:
if name == '__pycache__':
prune(os.path.join(root, name))
for name in files:
if (name.endswith('.pyc') or name.endswith('.pyo')) and 'test' not in root:
remove(os.path.join(root, name))
# remove release stamp
remove(STAMP_FILE)
def make_ready():
"""Prepare for sdist and wheel builds."""
make_clean()
stamp_release()
make_docs()
def make_docs():
"""Build manfile, usage, html and pdf documentation."""
mkdir(DOC_PATH)
make_man(DOC_PATH, MAN_NAME)
make_htmldoc(DOC_PATH, HTMLDOC_NAME)
make_usage(USAGE_PATH, USAGE_NAME)
def make_local():
"""Build manfile, usage, html and pdf documentation."""
make_usage(USAGE_PATH, USAGE_NAME)
###############################################################################
# release stamp
# git commit hash
try:
TAG = check_output(['git', 'describe', '--tags'], cwd=HERE).strip().decode('ascii', 'ignore')
COMMIT = check_output(
['git', 'describe', '--always'], cwd=HERE
).strip().decode('ascii', 'ignore')
except (EnvironmentError, CalledProcessError):
TAG = u''
COMMIT = u''
# https://reproducible-builds.org/specs/source-date-epoch/
try:
TIMESTAMP = str(datetime.datetime.utcfromtimestamp(int(os.environ['SOURCE_DATE_EPOCH'])))
except KeyError:
TIMESTAMP = str(datetime.datetime.now())
# release info
RELEASE_ID = {
u'version': VERSION,
u'tag': TAG,
u'commit': COMMIT,
u'timestamp': TIMESTAMP
}
def stamp_release():
"""Place the relase ID file."""
json_str = json.dumps(RELEASE_ID)
with open(STAMP_FILE, 'w') as release_json:
release_json.write(json_str)
###############################################################################
# icon
def build_icon():
"""Create an icon file for the present platform."""
flat = (_b for _row in ICON for _b in _row)
rgb = ((_b*255,)*3 for _b in flat)
rgbflat = (_b for _tuple in rgb for _b in _tuple)
imgstr = bytes(rgbflat)
width, height = len(ICON[0]), len(ICON)
img = Image.frombytes('RGB', (width, height), imgstr)
format = {'win32': 'ico', 'darwin': 'icns'}.get(sys.platform, 'png')
icon_file = ICON_PATTERN.format(format=format)
with os_safe('building icon file', icon_file):
img.resize((width*2, height*2)).save(icon_file)
###############################################################################
# shell utilities
@contextmanager
def os_safe(message, name):
"""Catch and report environment errors."""
print('... {} {} ... '.format(message, name), end='')
try:
yield
except EnvironmentError as err:
print(err)
else:
print('ok')
def prune(path):
"""Recursively remove a directory."""
with os_safe('pruning', path):
shutil.rmtree(path)
def remove(path):
"""Remove a file."""
with os_safe('removing', path):
os.remove(path)
def mkdir(name):
"""Create a directory and all parents needed (mkdir -p)."""
with os_safe('creating', name):
os.makedirs(name)
|