812 lines
30 KiB
Python
Executable File
812 lines
30 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
|
|
|
u"""%(name)s - Gérer les releases d'un projet
|
|
USAGE
|
|
%(name)s [options] [projdir]
|
|
"""
|
|
|
|
import os, sys, string, re, shutil
|
|
from os import path
|
|
from time import time, localtime
|
|
|
|
from ulib.all import *
|
|
from ulib.vcs import *
|
|
from ulib.ext.tarfile import tarfile
|
|
from uapps.update_inc import update_inc_params, update_inc
|
|
from pyutools.TODO import TODOs
|
|
from pyutools.wo import wo_projname_and_projtype
|
|
|
|
def uappspath(file):
|
|
"""Exprimer un fichier par rapport au répertoire de uapps.
|
|
XXX il vaut mieux déplacer la logique dans un module, et exprimer le fichier
|
|
par rapport à la position du module
|
|
"""
|
|
uappsdir = path.join(sys.prefix, 'lib', 'python' + sys.version[:3], 'site-packages', 'uapps')
|
|
return path.join(uappsdir, file)
|
|
|
|
##################################################
|
|
### Méthodes de gestion des paramètres
|
|
|
|
# Note: les paramètres peuvent être donnés sur la ligne de commande, dans un
|
|
# fichier release.conf, et ont une valeur par défaut
|
|
|
|
## On construit les structures au fur et à mesure
|
|
# Paramètres valides dans release.conf et méthodes associées
|
|
params_for_release_conf = {}
|
|
|
|
# Valeurs par défauts
|
|
params_defaults = {}
|
|
|
|
# Paramètres sur la ligne de commande
|
|
argsdesc = [
|
|
# Afficher l'aide
|
|
('h', 'help', u"Afficher l'aide"),
|
|
]
|
|
|
|
## Gestion des versions
|
|
uver = 'release_update_version'
|
|
sver = 'force_version'
|
|
|
|
def set_uver(p, value=False): p[uver] = is_yes(value)
|
|
def set_sver(p, value): p[sver] = value
|
|
|
|
params_for_release_conf.update({uver: set_uver})
|
|
params_defaults.update({uver: False, sver: None})
|
|
argsdesc.extend([
|
|
('n', 'keep-version', u"Ne pas changer la version (par défaut)"),
|
|
('u', 'update-version', u"Mettre à jour la version"),
|
|
('v:', 'set-version=', u"Spécifier une nouvelle version"),
|
|
])
|
|
|
|
## suivi des changements
|
|
log = 'release_log_changes'
|
|
todorc = 'release_todorc'
|
|
|
|
def set_log(p, value=False): p[log] = is_yes(value)
|
|
def set_todorc(p, value): p[todorc] = path.abspath(value)
|
|
|
|
params_for_release_conf.update({log: set_log, todorc: set_todorc})
|
|
params_defaults.update({log: True, todorc: None})
|
|
argsdesc.extend([
|
|
(None, 'log-changes', u"Saisir les changements pour une nouvelle version (par défaut)"),
|
|
(None, 'no-log-changes', u"Ne pas saisir les changements"),
|
|
(None, 'todorc=', u"Spécifier le fichier de configuration pour la gestion des TODOs"),
|
|
])
|
|
|
|
## gestion des tags (avec CVS)
|
|
tag = 'release_tag_in_vcs'
|
|
stag = 'force_tag'
|
|
|
|
def set_tag(p, value=False): p[tag] = is_yes(value)
|
|
def set_stag(p, value): p[stag] = value
|
|
|
|
params_for_release_conf.update({tag: set_tag})
|
|
params_defaults.update({tag: True, stag: None})
|
|
argsdesc.extend([
|
|
(None, 'tag', u"tagger les sources avec le nom du projet et la version (par défaut)"),
|
|
(None, 'no-tag', u"Ne pas tagger les sources"),
|
|
(None, 'set-tag', u"Spécifier le tag à utiliser"),
|
|
])
|
|
|
|
## gestion des fichiers de licence
|
|
lic = 'release_include_license'
|
|
slic = 'force_license'
|
|
|
|
def set_lic(p, value=False): p[lic] = is_yes(value)
|
|
def set_slic(p, value): p[slic] = value
|
|
|
|
params_for_release_conf.update({lic: set_lic})
|
|
params_defaults.update({lic: True, slic: None})
|
|
argsdesc.extend([
|
|
(None, 'include-license', u"Inclure un fichier de licence"),
|
|
(None, 'no-include-license', u"Ne pas inclure le fichier de licence"),
|
|
(None, 'set-license', u"Choisir le fichier de licence à utiliser"),
|
|
(None, 'ur', u"Spécifier la licence pour les produits de l'université de la Réunion"),
|
|
(None, 'jclain', u"Spécifier la licence pour les produits de Jephté CLAIN"),
|
|
])
|
|
|
|
## création de l'archive
|
|
bp = 'release_base_path'
|
|
exn = 'release_exclude_names'
|
|
exp = 'release_exclude_paths'
|
|
an = 'archive_name'
|
|
asrc = 'archive_source'
|
|
sas = 'source_archive_suffix'
|
|
ab = 'archive_binary'
|
|
bas = 'binary_archive_suffix'
|
|
aj = 'archive_javadoc'
|
|
jdas = 'archive_suffix'
|
|
|
|
def set_bp(p, value): p[bp] = value
|
|
def set_exn(p, value):
|
|
if not p.has_key(exn): p[exn] = []
|
|
if ':' in value:
|
|
values = string.split(value, ':')
|
|
else:
|
|
values = [value]
|
|
for value in values:
|
|
list_set(p[exn], value)
|
|
def set_exp(p, value):
|
|
if not p.has_key(exp): p[exp] = []
|
|
if ':' in value:
|
|
values = string.split(value, ':')
|
|
else:
|
|
values = [value]
|
|
for value in values:
|
|
list_set(p[exp], value)
|
|
def set_an(p, value): p[an] = value
|
|
def set_asrc(p, value=False): p[asrc] = is_yes(value)
|
|
def set_sas(p, value): p[sas] = value
|
|
def set_ab(p, value=False): p[ab] = is_yes(value)
|
|
def set_bas(p, value): p[bas] = value
|
|
def set_aj(p, value=False): p[aj] = is_yes(value)
|
|
def set_jdas(p, value): p[jdas] = value
|
|
|
|
params_for_release_conf.update({bp: set_bp, exn: set_exn, exp: set_exp})
|
|
params_defaults.update({bp: '@@projname@@', #'@@projname@@-@@projver@@',
|
|
exn: [], exp: [], an: None,
|
|
asrc: False, sas: '',
|
|
ab: False, bas: '',
|
|
aj: False, jdas: '-api'
|
|
})
|
|
argsdesc.extend([
|
|
('c', 'archive-source', u"Créer l'archive du source"),
|
|
('B', 'archive-binary', u"Créer l'archive du produit binaire"),
|
|
('J', 'archive-javadoc', u"Créer l'archive de la javadoc"),
|
|
('b:', 'base-path=', u"Dans l'archive générée, répertoire de base"),
|
|
('x:', 'exclude-names=', u"Noms de fichiers à exclure de l'archive"),
|
|
('X:', 'exclude-paths=', u"Répertoires à exclure de l'archive"),
|
|
])
|
|
|
|
# ensembles nommés d'options
|
|
pyp = 'release_use_python_defaults'
|
|
zop = 'release_use_zope_defaults'
|
|
jawop = 'release_use_jawo_defaults'
|
|
|
|
def set_pyp(p, value=False):
|
|
if is_yes(value):
|
|
p[pyp] = True
|
|
p[zop] = p[jawop] = False
|
|
set_tag(p, True)
|
|
set_log(p, True)
|
|
set_lic(p, True)
|
|
set_exn(p, 'CVS/')
|
|
set_exn(p, '.svn/')
|
|
set_exn(p, '*.pyc')
|
|
set_exn(p, '*~')
|
|
set_exn(p, '.DS_Store')
|
|
def set_zop(p, value=False):
|
|
if is_yes(value):
|
|
set_pyp(True)
|
|
p[zop] = True
|
|
p[pyp] = p[jawop] = False
|
|
p[bp] = 'lib/python/Products/@@projname@@' #'lib/python/Products/@@projname@@-@@projver@@'
|
|
def set_jawop(p, value=False):
|
|
if is_yes(value):
|
|
p[jawop] = True
|
|
p[pyp] = p[zop] = False
|
|
set_tag(p, True)
|
|
set_log(p, True)
|
|
set_lic(p, True)
|
|
set_sas(p, '-src')
|
|
set_exn(p, 'CVS/')
|
|
set_exn(p, '.svn/')
|
|
set_exn(p, '*~')
|
|
set_exn(p, '.DS_Store')
|
|
set_exp(p, 'build/')
|
|
set_exp(p, '*.pbproj/*.pbxuser')
|
|
|
|
params_for_release_conf.update({pyp: set_pyp, zop: set_zop, jawop: set_jawop})
|
|
params_defaults.update({pyp: False, zop: False, jawop: False})
|
|
argsdesc.extend([
|
|
(None, 'auto', u"Autodétecter le type de projet (par défaut)"),
|
|
(None, 'no-auto', u"Ne pas autodétecter le type de projet"),
|
|
('P', 'python-product', u"Choisir les options par défaut pour un projet Python"),
|
|
('Z', 'zope-product', u"Choisir les options par défaut pour un projet Zope"),
|
|
('W', 'jawo-product', u"Choisir les options par défaut pour un projet jabuild/wobuild"),
|
|
])
|
|
|
|
def is_auto(opt): return opt in ('--auto', )
|
|
def isnot_auto(opt): return opt in ('--no-auto', '-P', '--python-product',
|
|
'-Z', '--zope-product',
|
|
'-W', '--jawo-product',
|
|
)
|
|
|
|
# support de update_inc
|
|
ui = 'update_inc'
|
|
|
|
def set_ui(p, value=False, basedir=None):
|
|
p[ui] = None
|
|
if is_yes(value):
|
|
if basedir is None: basedir = os.getcwd()
|
|
cf = ShConfigFile(path.join(basedir, '.uinst.conf'), raise_exception=False)
|
|
if cf.is_valid():
|
|
# on force le mode quiet avec -q en attendant que update_inc supporte set_verbosity()
|
|
options = split_args(cf.get('update_inc_options', '') + ' -q') + split_args(cf.get('update_inc_args', ''))
|
|
p_ui, args_ui = update_inc_params(options, basedir=basedir)
|
|
p[ui] = (p_ui, args_ui)
|
|
def fold_maybe(p, vcsdir=None):
|
|
if p[ui]:
|
|
pr, args = p[ui]
|
|
if vcsdir is None:
|
|
update_inc(pr, args, 'fold')
|
|
return True
|
|
else: return vcsdir.fold(pr, args, 'fold')
|
|
def unfold_maybe(p, vcsdir=None):
|
|
if p[ui]:
|
|
pr, args = p[ui]
|
|
if vcsdir is None: update_inc(pr, args, 'unfold')
|
|
else: vcsdir.unfold(pr, args, 'unfold')
|
|
|
|
params_for_release_conf.update({ui: set_ui})
|
|
params_defaults.update({ui: False})
|
|
argsdesc.extend([
|
|
(None, 'update_inc', u"Activer le support de update_inc"),
|
|
])
|
|
|
|
# autres paramètres qui sont utilisés par release()
|
|
cver = 'version_txt_created?'
|
|
clog = 'changes_txt_created?'
|
|
clic = 'license_txt_created?'
|
|
cd = 'curdir'
|
|
pn = 'projname'
|
|
pt = 'projtype'
|
|
|
|
def set_cver(p, value=False): p[cver] = is_yes(value)
|
|
def set_clog(p, value=False): p[clog] = is_yes(value)
|
|
def set_clic(p, value=False): p[clic] = is_yes(value)
|
|
def set_cd(p, value): p[cd] = value
|
|
def set_pn(p, value): p[pn] = value
|
|
def set_pt(p, value): p[pt] = value
|
|
|
|
params_for_release_conf.update({})
|
|
|
|
params_defaults.update({cver: False,clog: False, clic: False, cd: None,
|
|
pn: None, pt: None,
|
|
})
|
|
argsdesc.extend([])
|
|
|
|
##################################################
|
|
# Méthodes utilitaires
|
|
def list_set(list, value):
|
|
"""insérer une valeur dans une liste si elle n'y est pas déjà
|
|
"""
|
|
if value not in list:
|
|
list.append(value)
|
|
|
|
def strip_release_from_version(v):
|
|
"""Supprimer la date de release dd/mm/yyyy d'un numéro de version
|
|
"""
|
|
v = string.strip(v)
|
|
mo = re.match(r'(.*)-r[0-9]+/[0-9]+/[0-9]+$', v)
|
|
if mo is not None:
|
|
v = mo.group(1)
|
|
return v
|
|
|
|
def _inc_last(v):
|
|
v0, v1 = re.match(r'^(.*?)([0-9]*)$', v).groups()
|
|
v1 = str(int(v1 or 0) + 1)
|
|
return v0 + v1
|
|
|
|
def increment_version(v):
|
|
"""Incrémenter le dernier élément d'un numéro de version, et retourner les
|
|
différentes propositions possibles.
|
|
"""
|
|
vs = []
|
|
# [0]: ajouter un chiffre
|
|
vs.append(v + '.1')
|
|
# [1]: incrémenter le dernier
|
|
vs.append(_inc_last(v))
|
|
# [2]: incrémenter l'avant dernier
|
|
if v.count('.') > 1:
|
|
pos = v.rfind('.')
|
|
vs.append(_inc_last(v[:pos]))
|
|
|
|
return vs
|
|
|
|
def expand_projname_and_version(s, p):
|
|
s = string.replace(s, "@@projname@@", p[pn])
|
|
s = string.replace(s, "@@projver@@", p[sver])
|
|
return s
|
|
|
|
##################################################
|
|
# Méthodes pour la création d'une archive
|
|
def tarpath(p):
|
|
"""normaliser un chemin pour utilisation dans un fichier tar: il ne doit pas
|
|
se terminer par /, les séparateurs doivent être / et non \ et le répertoire
|
|
courant '.' est remplacé par ''
|
|
"""
|
|
p = path.normpath(p)
|
|
p = string.replace(p, '\\', '/')
|
|
while p[-1:] == '/': p = p[:-1]
|
|
if p == '.': p = ''
|
|
return p
|
|
|
|
def normalize_paths(paths):
|
|
"""Normaliser tous les chemins de la liste paths.
|
|
|
|
Retourner une liste de tuples (tpath, apath), où tpath est le chemin
|
|
normalisé pour le fichier tar (séparateurs '/'), et apath est le chemin
|
|
absolu sur le système de fichier.
|
|
"""
|
|
return map(lambda p: (tarpath(p), path.abspath(p)), paths)
|
|
|
|
def select_files(p, exclude_names=(), exclude_paths=(), basepath=None):
|
|
"""Construire récursivement la liste de tous les fichiers à partir du
|
|
répertoire p, en excluant les répertoires dont le nom est donné dans
|
|
excludes_names, ou dont le chemin relativement à p est donné dans
|
|
exclude_paths
|
|
|
|
on ne suit pas les liens symboliques.
|
|
|
|
Note: on exclue automatiquement les fichiers qui sont 'binaires'
|
|
"""
|
|
if basepath is None:
|
|
return select_files(p, exclude_names, exclude_paths, p)
|
|
else:
|
|
files = []
|
|
for file in os.listdir(p):
|
|
pf = path.join(p, file) # chemin absolu
|
|
relpf = pf[len(basepath) + 1:] # chemin relativement à basepath. +1 pour le '/'
|
|
|
|
ignore = 0
|
|
for pattern in exclude_names:
|
|
if matches_name(pattern, file, p):
|
|
ignore = 1
|
|
else:
|
|
for pattern in exclude_paths:
|
|
if matches_name(pattern, relpf, basepath):
|
|
ignore = 1
|
|
if not ignore:
|
|
files.append(pf)
|
|
if path.isdir(pf) and not path.islink(pf):
|
|
files.extend(select_files(pf, exclude_names, exclude_paths, basepath))
|
|
return files
|
|
|
|
def create_archive(p, paths, altbp=None):
|
|
"""Créer l'archive p[an].tgz dans le répertoire courant avec les
|
|
répertoires énumérés dans paths
|
|
"""
|
|
archive = tarfile.open(p[an] + '.tgz', 'w:gz')
|
|
archive.posix = 0
|
|
|
|
if altbp is None: altbp = p[bp]
|
|
basepath = altbp and altbp + '/' or ''
|
|
for tp, ap in paths:
|
|
tp = tp and tp + '/' or ''
|
|
files = select_files(ap, p[exn], p[exp])
|
|
for file in files:
|
|
tfile = basepath + tp + string.replace(file[len(ap) + 1:], '\\', '/')
|
|
archive.add(file, tfile, False)
|
|
|
|
archive.close()
|
|
|
|
##################################################
|
|
# L'application release
|
|
|
|
def print_help(name=None):
|
|
if name is None:
|
|
name = path.split(sys.argv[0])[1]
|
|
print __doc__ % vars()
|
|
print "OPTIONS"
|
|
for argdesc in argsdesc:
|
|
so = argdesc[0:1] and argdesc[0] or None
|
|
if so is not None: so = '-' + so[:1]
|
|
|
|
lo = argdesc[1:2] and argdesc[1] or None
|
|
if lo is not None: lo = (so is not None and ', ' or '') + '--' + lo
|
|
|
|
desc = argdesc[2:3] and argdesc[2] or None
|
|
|
|
if so is not None or lo is not None:
|
|
print " %s%s" % (so or '', lo or '')
|
|
if desc is not None: print " %s" % desc
|
|
|
|
def release(p, *paths):
|
|
release_date = time()
|
|
|
|
# Obtenir, vérifier et/ou normaliser les chemins
|
|
paths = list(paths)
|
|
|
|
curdir = p[cd]
|
|
if curdir is None: die(u"curdir doit être spécifié")
|
|
projname = p[pn]
|
|
if projname is None: die(u"Il faut donner le nom du projet")
|
|
projtype = p[pt]
|
|
|
|
tp0, ap0 = paths[0]
|
|
|
|
### obtenir le numéro de version courant et incrémenter le numéro de version
|
|
maj_version = False # True si la version a été changée
|
|
|
|
# calculer le nom du fichier qui contient la version
|
|
version_txt = 'VERSION.txt'
|
|
if not path.isfile(path.join(ap0, version_txt)):
|
|
if path.isfile(path.join(ap0, 'version.txt')):
|
|
version_txt = 'version.txt'
|
|
aversion_txt = path.join(ap0, version_txt)
|
|
|
|
# lire la version qui se trouve dans le fichier
|
|
if not path.isfile(aversion_txt):
|
|
if not ask_yesquit(u"Le fichier %s n'existe pas. Faut-il le créer?" % version_txt, True, minlevel=I_INTER):
|
|
raise Exit, 1
|
|
set_cver(p, True)
|
|
|
|
vf = TextFile(aversion_txt, raise_exception=False)
|
|
if vf.lines:
|
|
v = strip_release_from_version(vf.lines[0])
|
|
einfo(u"La version actuelle est %s" % v)
|
|
else:
|
|
v = ''
|
|
if p[uver]:
|
|
v = '0.0'
|
|
einfo(u"Pas d'information de version. on commence à 0.1")
|
|
elif p[sver] is not None:
|
|
einfo(u"Pas d'information de version. On commence à %s (forcé)" % p[sver])
|
|
else:
|
|
einfo(u"Pas d'information de version.")
|
|
|
|
vs = None
|
|
if p[sver] is None:
|
|
if p[uver]:
|
|
vs = increment_version(v)
|
|
v = vs[1]
|
|
maj_version = True
|
|
p[sver] = v
|
|
else:
|
|
maj_version = p[sver] != v
|
|
|
|
if maj_version:
|
|
if vs is None:
|
|
if not ask_yesquit(u"Mettre à jour la version vers %s?" % p[sver], True):
|
|
raise Exit, 1
|
|
else:
|
|
if check_verbosity(I_NORMAL):
|
|
for i in range(len(vs)):
|
|
einfo(u"%i - Passer à la version %s" % (i, vs[i]))
|
|
r = read_value(u"Mettre à jour la version vers %s? [Oq]" % p[sver], default="O", refuse_empty=False)
|
|
v = None
|
|
if re.match(r'\d+$', r) is not None:
|
|
i = int(r)
|
|
if i >= 0 and i < len(vs):
|
|
v = vs[i]
|
|
elif is_yes(r):
|
|
v = vs[1]
|
|
|
|
if v is None: raise Exit, 1
|
|
else: p[sver] = v
|
|
|
|
vf.lines[0:1] = [p[sver] + '-r' + datef(FR_DATEF, release_date)]
|
|
vf.writelines()
|
|
|
|
# mettre à jour p[bp] qui peut contenir des tags @@projname@@ et @@projver@@
|
|
p[bp] = expand_projname_and_version(tarpath(p[bp]), p)
|
|
|
|
### enregistrer les changements, seulement si on met à jour la version
|
|
if p[log] and maj_version:
|
|
if not ask_yesquit(u"Saisir le changelog pour la version %s?" % p[sver], True, minlevel=I_INTER):
|
|
raise Exit, 1
|
|
|
|
template = u"""
|
|
-EDIT-: ----------------------------------------------------------------------
|
|
-EDIT-: Saisissez une description de la distribution %(projname)s-%(version)s
|
|
-EDIT-:
|
|
-EDIT-: Les lignes commencant par '-EDIT-:' sont automatiquement supprimees
|
|
-EDIT-: dans le fichier de sortie (%(filename)s)
|
|
-EDIT-:
|
|
-EDIT-: ----------------------------------------------------------------------
|
|
"""
|
|
project = path.split(ap0)[1]
|
|
# Calculer automatiquement les tâches qui doivent être incluses dans le log
|
|
ts = TODOs(p[todorc])
|
|
ts.purge(project)
|
|
released = ts.list_releaseable(project)
|
|
|
|
mark_released = False
|
|
if released:
|
|
mark_released = True
|
|
template = template + u"""\
|
|
-EDIT-: Pour information, les informations suivantes seront automatiquement
|
|
-EDIT-: ajoutées à la description de la distribution:
|
|
-EDIT-:
|
|
"""
|
|
lines = []
|
|
for ds in map(lambda d: d.to_string(), released):
|
|
ds = ds.replace("%", "%%")
|
|
lines.append("\n".join(map(lambda l: "-EDIT-: " + l, string.split(ds, "\n"))))
|
|
template = template + "\n-EDIT-: ----\n".join(lines)
|
|
|
|
todofile = ts.get_todofile(project)
|
|
if todofile is not None and todofile.can_release():
|
|
# éditer le fichier
|
|
change_lines = edit_template(template % {'projname': projname,
|
|
'version': p[sver],
|
|
'filename': todofile.get_file()},
|
|
'-EDIT-:')
|
|
todofile.release(released, release_date, p[sver], change_lines.join())
|
|
todofile.save()
|
|
|
|
else:
|
|
# calculer le nom du fichier qui contient les changements
|
|
changes_txt = 'CHANGES.txt'
|
|
if not path.exists(path.join(ap0, changes_txt)):
|
|
if path.exists(path.join(ap0, 'changes.txt')):
|
|
changes_txt = changes.txt
|
|
achanges_txt = path.join(ap0, changes_txt)
|
|
|
|
if not path.exists(achanges_txt):
|
|
set_clog(p, True)
|
|
|
|
# éditer le fichier
|
|
change_lines = edit_template(template % {'projname': projname,
|
|
'version': p[sver],
|
|
'filename': changes_txt},
|
|
'-EDIT-:')
|
|
if released:
|
|
first = False
|
|
if change_lines: change_lines.append("")
|
|
else: first = True
|
|
for todo in released:
|
|
if first: first = False
|
|
else: change_lines.append("----")
|
|
change_lines.extend(string.split(todo.to_string(), "\n"))
|
|
|
|
# ajouter le changelog
|
|
cf = TextFile(achanges_txt, raise_exception=False)
|
|
|
|
if cf.lines and string.strip(cf.lines[-1]):
|
|
cf.lines.append("")
|
|
title = u"%s %s-%s" % (datef(FRHM_DATEF, release_date), projname, p[sver])
|
|
cf.lines[0:0] = [title, "=" * len(title), ""] + change_lines + (cf.lines and [""] or [])
|
|
|
|
cf.writelines()
|
|
|
|
ts.mark_released(project)
|
|
|
|
### ajouter le fichier de licence
|
|
alicense_txt = None
|
|
if p[lic] and maj_version:
|
|
# calculer le nom du fichier qui contient la licence
|
|
license_txt = 'LICENSE.txt'
|
|
if not path.exists(path.join(ap0, license_txt)):
|
|
if path.exists(path.join(ap0, 'license.txt')):
|
|
license_txt = 'license.txt'
|
|
alicense_txt = path.join(ap0, license_txt)
|
|
|
|
if not path.exists(alicense_txt):
|
|
set_clic(p, True)
|
|
|
|
# si on a spécifié un fichier de license, forcer l'écrasement. Sinon, on
|
|
# ne copie la license que si elle n'existe pas déjà
|
|
copy_license = False
|
|
if p[slic] is None:
|
|
if p[clic]:
|
|
copy_license = True
|
|
p[slic] = uappspath('ur_license.txt') # par défaut
|
|
if p[slic] is not None:
|
|
copy_license = True
|
|
if dirname(p[slic]) == '':
|
|
# si on donne juste le nom du fichier, et qu'il n'existe pas
|
|
# dans le répertoire courant, l'exprimer par rapport à au
|
|
# répertoire du module uapps. XXX cf la documentation de
|
|
# uappspath() pour voir ce qu'il faut corriger
|
|
if not path.isfile(path.join(curdir, p[slic])):
|
|
p[slic] = tmp = uappspath(p[slic])
|
|
if not path.isfile(tmp):
|
|
tmp = tmp + '_license.txt'
|
|
if path.exists(tmp): p[slic] = tmp
|
|
## si on donne juste le nom du fichier, et qu'il n'existe pas
|
|
## dans le répertoire courant, l'exprimer par rapport à
|
|
## au répertoire de ce module
|
|
#if not path.isfile(path.join(curdir, p[slic])):
|
|
# p[slic] = path.join(dirname(__file__), p[slic])
|
|
# if not path.isfile(p[slic]):
|
|
# tmp = p[slic] + '_license.txt'
|
|
# if path.exists(tmp):
|
|
# p[slic] = tmp
|
|
if not path.isfile(p[slic]):
|
|
die(u"Fichier de licence inexistant: %s" % p[slic])
|
|
|
|
if copy_license:
|
|
if not ask_yesquit(u"Copier le fichier de licence %s?" % basename(p[slic]), True, minlevel=I_INTER):
|
|
raise Exit, 1
|
|
|
|
if not path.exists(alicense_txt) or not path.samefile(p[slic], alicense_txt):
|
|
shutil.copy(p[slic], alicense_txt)
|
|
p[slic] = alicense_txt
|
|
|
|
lf = TextFile(p[slic])
|
|
if lf.grep(r'.*(@@projname@@|@@year@@)'):
|
|
lf.replace('@@projname@@', projname)
|
|
lf.replace('@@year@@', datef(YEAR_DATEF))
|
|
lf.writelines()
|
|
|
|
### tag des fichiers si VCS est activé et si la version a été modifiée
|
|
if p[tag] and maj_version and is_vcsdir(ap0):
|
|
vcsdir = get_vcsdir(ap0)
|
|
vcsdir.set_tag_rootdir(False)
|
|
options = dict(trace=True, quiet=True, auto_update=False)
|
|
|
|
# déterminer le nom du tag
|
|
if p[stag] is None:
|
|
p[stag] = '%s-%s' % (projname, string.replace(p[sver], '.', '-'))
|
|
|
|
if ask_yesno(u"Taguer avec %s?" % p[stag], True, minlevel=I_INTER):
|
|
os.chdir(ap0)
|
|
|
|
# avant vcs, il faut faire un fold si on supporte update_inc
|
|
estep(u"Suppression des inclusions")
|
|
folded = fold_maybe(p, vcsdir)
|
|
|
|
estep(u"Marquage des fichiers avec le tag %s" % p[stag])
|
|
if p[cver]: vcsdir.add(version_txt, **options)
|
|
if p[clog]: vcsdir.add(changes_txt, **options)
|
|
if p[clic]: vcsdir.add(license_txt, **options)
|
|
|
|
message = u"Création de la distribution pour le tag %s" % p[stag]
|
|
vcsdir.commit(message, **options)
|
|
vcsdir.tag(p[stag], message, **options)
|
|
|
|
# après vcs, il faut faire un unfold si on supporte update_inc
|
|
if folded:
|
|
estep(u"Mise à jour des inclusions")
|
|
unfold_maybe(p, vcsdir)
|
|
|
|
### Créer la distribution si cela a été demandé
|
|
# vérifier la cohérence de certains paramètres
|
|
if p[asrc] or p[ab] or p[aj]:
|
|
if (p[ab] or p[aj]) and not p[jawop]:
|
|
ewarn(u"La création d'une archive binaire/javadoc n'est supportée que pour les projets WebObjects")
|
|
|
|
# avant de créer l'archive, unfold si on supporte update_inc
|
|
unfold_maybe(p)
|
|
|
|
# contruire l'archive du source
|
|
p[an] = '%s-%s%s' % (projname, p[sver], p[sas])
|
|
if ask_yesno(u"Créer l'archive %s.tgz?" % p[an], True, minlevel=I_INTER):
|
|
if p[bp] != '':
|
|
# si on a spécifié un chemin de base, inclure la racine de façon relative
|
|
paths[0] = ('', paths[0][1])
|
|
estep(u"Création de l'archive %s.tgz" % p[an])
|
|
create_archive(p, paths)
|
|
|
|
# construire l'archive du binaire
|
|
if p[ab] and p[jawop]:
|
|
archive_created = False
|
|
for suffix in ('', '.woa', '.framework'):
|
|
projdir = projname + suffix
|
|
p[an] = '%s-%s%s' % (projdir, p[sver], p[bas])
|
|
aprojdir = path.join(ap0, 'build', projdir)
|
|
if path.isdir(aprojdir):
|
|
if ask_yesno(u"Créer l'archive des binaires %s.tgz?" % p[an], True, minlevel=I_INTER):
|
|
estep(u"Création de l'archive %s.tgz" % p[an])
|
|
create_archive(p, [(projdir, aprojdir)], '')
|
|
archive_created = True
|
|
if not archive_created:
|
|
eerror(u"Imposssible de créer l'archive binaire: il faut construire le projet d'abord")
|
|
|
|
# construire l'archive de la documentation
|
|
if p[aj] and p[jawop]:
|
|
p[an] = '%s-%s%s' % (projname, p[sver], p[jdas])
|
|
aprojdir = path.join(ap0, 'build', 'api')
|
|
if path.isdir(aprojdir):
|
|
if ask_yesno(u"Créer l'archive de la documentation %s.tgz?" % p[an], True, minlevel=I_INTER):
|
|
estep(u"Création de l'archive %s.tgz" % p[an])
|
|
create_archive(p, [(projdir, aprojdir)])
|
|
else:
|
|
eerror(u"Répertoire de la javadoc introuvable. L'archive n'a pas été générée")
|
|
|
|
def run_release():
|
|
### Lire les arguments
|
|
options, longoptions = build_options(argsdesc)
|
|
opts, paths = get_args(None, options, longoptions)
|
|
p = {}
|
|
|
|
# todorc est traité à part, parce qu'il peut s'agir d'un répertoire relatif
|
|
index = 0
|
|
for opt, value in opts:
|
|
if opt in ('--todorc', ):
|
|
set_todorc(p, value)
|
|
del opts[index]
|
|
break
|
|
index = index + 1
|
|
|
|
### Vérifier et normaliser les chemins
|
|
if not paths: paths = ['.']
|
|
paths = normalize_paths(paths)
|
|
tp0, ap0 = paths[0]
|
|
|
|
curdir = os.getcwd()
|
|
if curdir[:len(ap0)] == ap0:
|
|
# nous sommes dans le répertoire de la distribution, se placer au-dessus
|
|
os.chdir(path.split(ap0)[0])
|
|
p[cd] = curdir
|
|
|
|
### Lecture des paramètres
|
|
# Si nécessaire, auto-détecter le type de projet
|
|
# ne pas autodétecter s'il existe un fichier .uinst.conf
|
|
rcf = ShConfigFile(path.join(ap0, '.uinst.conf'), raise_exception=False)
|
|
|
|
projname = projtype = None
|
|
projecttype_autodetected = False
|
|
autodetect_projecttype = not rcf.is_valid()
|
|
for opt, value in opts:
|
|
if is_auto(opt): autodetect_projecttype = True
|
|
elif isnot_auto(opt): autodetect_projecttype = False
|
|
|
|
if autodetect_projecttype:
|
|
projname, projtype = wo_projname_and_projtype(ap0)
|
|
if projtype in ('woapplication', 'woframework'):
|
|
projecttype_autodetected = True
|
|
set_jawop(p, True)
|
|
|
|
if projname is None:
|
|
# sinon, le nom de base de l'archive est le nom du répertoire
|
|
projname = path.split(ap0)[1]
|
|
p[pn] = projname
|
|
p[pt] = projtype
|
|
|
|
# Tout d'abord, essayer de lire les paramètres dans un fichier .uinst.conf
|
|
if rcf.is_valid():
|
|
for key, set_func in params_for_release_conf.items():
|
|
if not rcf.has_key(key): continue
|
|
|
|
value = rcf[key]
|
|
if key == ui:
|
|
# cas particulier: ui demande le répertoire de base
|
|
set_func(p, value, ap0)
|
|
else:
|
|
set_func(p, value)
|
|
|
|
# puis lire les paramètres en ligne de commande
|
|
for opt, value in opts:
|
|
if opt in ('-h', '--help'):
|
|
print_help()
|
|
sys.exit(0)
|
|
|
|
elif opt in ('-n', '--keep-version'): set_uver(p, False)
|
|
elif opt in ('-u', '--update-version'): set_uver(p, True)
|
|
elif opt in ('-v', '--set-version'): set_sver(p, value)
|
|
|
|
elif opt in ('--log-changes', ): set_log(p, True)
|
|
elif opt in ('--no-log-changes', ): set_log(p, False)
|
|
|
|
elif opt in ('--tag', ): set_tag(p, True)
|
|
elif opt in ('--no-tag', ): set_tag(p, False)
|
|
elif opt in ('--set-tag', ): set_stag(p, value)
|
|
|
|
elif opt in ('--include-license', ): set_lic(p, True)
|
|
elif opt in ('--no-include-license', ): set_lic(p, False)
|
|
elif opt in ('--set-license', ): set_slic(p, value)
|
|
elif opt in ('--ur', ): set_slic(p, 'ur_license.txt')
|
|
elif opt in ('--jclain', ): set_slic(p, 'jclain_license.txt')
|
|
|
|
elif opt in ('-c', '--archive-source'): set_asrc(p, True)
|
|
elif opt in ('-B', '--archive-binary'): set_ab(p, True)
|
|
elif opt in ('-J', '--archive-javadoc'): set_aj(p, True)
|
|
elif opt in ('-b', '--base-path'): set_bp(p, value)
|
|
elif opt in ('-x', '--exclude-names'): set_exn(p, value)
|
|
elif opt in ('-X', '--exclude-paths'): set_exp(p, value)
|
|
|
|
elif opt in ('-P', '--python-product'): set_pyp(p, True)
|
|
elif opt in ('-Z', '--zope-product'): set_zop(p, True)
|
|
elif opt in ('-W', '--jawo-product'): set_jawop(p, True)
|
|
|
|
elif opt in ('--update_inc', ): set_ui(p, True, ap0)
|
|
|
|
# compléter avec les valeurs par défaut des paramètres
|
|
for key, value in params_defaults.items():
|
|
if not p.has_key(key):
|
|
p[key] = value
|
|
|
|
# lancer l'application
|
|
try:
|
|
exitcode = apply(release, tuple([p] + paths))
|
|
except Exit, exitcode:
|
|
pass
|
|
sys.exit(exitcode)
|
|
|
|
if __name__ == '__main__':
|
|
run_release()
|