2016-11-11 00:10:25 -05:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
2016-11-12 12:05:03 -05:00
|
|
|
import re
|
2016-11-11 21:55:53 -05:00
|
|
|
import os
|
2016-11-12 16:44:26 -05:00
|
|
|
import shutil
|
2016-11-11 21:55:53 -05:00
|
|
|
import subprocess
|
2016-11-13 16:09:16 -05:00
|
|
|
import hashlib
|
2016-11-13 17:31:08 -05:00
|
|
|
import datetime
|
2016-11-16 01:41:43 -05:00
|
|
|
import gnupg
|
2016-11-13 16:09:16 -05:00
|
|
|
import git # python-gitpython in AUR
|
|
|
|
import menu3 # python-menu3 in AUR
|
|
|
|
import jinja2 # python-jinja in community
|
2016-11-12 16:44:26 -05:00
|
|
|
from tempfile import gettempdir
|
2016-11-13 17:50:44 -05:00
|
|
|
from urllib.request import urlopen
|
2016-11-12 16:44:26 -05:00
|
|
|
|
|
|
|
import pprint
|
2016-11-11 21:55:53 -05:00
|
|
|
|
2016-11-11 00:10:25 -05:00
|
|
|
## SETTINGS ##
|
2016-11-13 16:09:16 -05:00
|
|
|
gpgkey = ['748231EBCBD808A14F5E85D28C004C2F93481F6B'] # https://wiki.archlinux.org/index.php/PKGBUILD#validpgpkeys
|
2016-11-11 00:10:25 -05:00
|
|
|
maintname = 'brent s. <bts[at]square-r00t[dot]net>' # your name and email address, feel free to obfuscate though
|
|
|
|
pkgbuild_dir = '/opt/dev/arch' # what dir do the pkgbuilds/AUR checkouts live?
|
|
|
|
aur_pkgs_dir = pkgbuild_dir # should be the dir where the aur_pkgs repo checkout lives. it's recommended you keep this the same as pkgbuild_dir
|
2016-11-16 01:41:43 -05:00
|
|
|
git_name = 'brent s.' # the name to use in AUR git commits
|
|
|
|
git_email = 'bts@square-r00t.net' # the email to use in AUR git commits
|
2016-11-11 00:10:25 -05:00
|
|
|
|
2016-11-11 21:55:53 -05:00
|
|
|
## BUILD THE GUI ##
|
|
|
|
def gui_init():
|
2016-11-12 12:05:03 -05:00
|
|
|
m = menu3.Menu(True)
|
|
|
|
pkgname = False
|
|
|
|
|
|
|
|
base_options = ["Add a new package to the AUR...",
|
|
|
|
"Sign a package's sources",
|
|
|
|
"Update/initialize submodules from your AUR account"]
|
|
|
|
base_menu = m.menu("How might I assist you today?", base_options, "Select an operation ('q' to quit):")
|
|
|
|
pkg = {}
|
|
|
|
if base_menu == 1:
|
|
|
|
add_options = ["Release/versioned",
|
|
|
|
"VCS (Git, SVN, Mercurial, etc.)"]
|
|
|
|
add_menu = m.menu("What type of package is this?", add_options, "Package type ('q' to quit):")
|
|
|
|
elif base_menu == 2:
|
|
|
|
pkg['oper'] = 'sign'
|
|
|
|
elif base_menu == 3:
|
|
|
|
pkg['oper'] = 'submodule'
|
|
|
|
|
|
|
|
# If they selected to add a package and it's a VCS package...
|
|
|
|
if add_menu == 2:
|
|
|
|
vcs_options = ["Git",
|
|
|
|
"Subversion (svn)",
|
|
|
|
"Mercurial (hg)",
|
|
|
|
"Bazaar (bzr)"]
|
|
|
|
vcs_menu = m.menu("What type of VCS system?", vcs_options, "VCS type ('q' to quit):")
|
2016-11-13 16:09:16 -05:00
|
|
|
pkg['name'] = input("\nWhat is the name of your package? (Exclude the '-git' etc. suffix, that will be added automatically later on)\n").lower()
|
2016-11-21 01:10:18 -05:00
|
|
|
srcurl = input(("\nWhat is the checkout URL for {0}?\n" +
|
2016-11-13 18:44:59 -05:00
|
|
|
"(Do not include the directory or VCS type prefix as per\n" +
|
|
|
|
"https://wiki.archlinux.org/index.php/VCS_package_guidelines#VCS_sources ...\n" +
|
2016-11-21 01:10:18 -05:00
|
|
|
"it will be added automatically)\n").format(pkg['name']))
|
2016-11-12 12:05:03 -05:00
|
|
|
pkg['vcstype'] = ["git",
|
|
|
|
"svn",
|
|
|
|
"hg",
|
|
|
|
"bzr"]
|
2016-11-21 01:10:18 -05:00
|
|
|
pkg['vcstype'] = pkg['vcstype'][vcs_menu-1]
|
|
|
|
pkg['srcurl'] = pkg['name'] + "::" + pkg['vcstype'] + '+' + srcurl
|
2016-11-13 16:09:16 -05:00
|
|
|
pkg['name'] = pkg['name'] + "-" + pkg['vcstype']
|
|
|
|
pkg['type'] = 'vcs'
|
2016-11-12 12:05:03 -05:00
|
|
|
pkg['srcfile'] = False
|
|
|
|
pkg['ver'] = False
|
2016-11-13 16:09:16 -05:00
|
|
|
pkg['srchash'] = 'SKIP'
|
|
|
|
pkg['src_dl'] = False
|
2016-11-12 12:05:03 -05:00
|
|
|
|
|
|
|
# If they're adding a release package...
|
|
|
|
elif add_menu == 1:
|
|
|
|
pkg['vcstype'] = False
|
|
|
|
pkg['name'] = input("\nWhat is the name of your package?\n").lower()
|
|
|
|
pkg['ver'] = input("\nWhat is the version of the release you are packaging for {0}?\n".format(pkg['name']))
|
|
|
|
pkg['srcurl'] = input("\nWhat is the full URL for the tarball/zip file/etc. for {0} (version {1})?\n".format(pkg['name'], pkg['ver']))
|
|
|
|
pkg['srcfile'] = re.sub('^\s*(https?|ftp).*/(.*)\s*$', '\\2', pkg['srcurl'], re.IGNORECASE)
|
2016-11-13 16:09:16 -05:00
|
|
|
pkg['type'] = 'release'
|
|
|
|
# And here's where we download the source file for hashing
|
2016-11-20 17:11:02 -05:00
|
|
|
os.makedirs(('{0}/.aur_pkgs'.format(gettempdir())), exist_ok = True)
|
2016-11-16 01:41:43 -05:00
|
|
|
pkg['src_dl'] = "{0}/.aur_pkgs/{1}".format(gettempdir(), pkg['srcfile'])
|
2016-11-13 16:09:16 -05:00
|
|
|
print("Please wait while we download {0} to {1}...\n".format(pkg['srcfile'], pkg['src_dl']))
|
2016-11-13 17:50:44 -05:00
|
|
|
datastream = urlopen(pkg['srcurl'])
|
2016-11-13 16:09:16 -05:00
|
|
|
data_in = datastream.read()
|
|
|
|
with open(pkg['src_dl'], "wb") as data_dl:
|
|
|
|
data_dl.write(data_in)
|
|
|
|
pkg['srchash'] = hashlib.sha512(open(pkg['src_dl'],'rb').read()).hexdigest()
|
2016-11-12 12:05:03 -05:00
|
|
|
|
|
|
|
# And this is stuff shared by both types.
|
|
|
|
pkg['desc'] = input("\nWhat is a short description of {0}?\n".format(pkg['name']))
|
|
|
|
pkg['site'] = input("\nWhat is {0}'s website?\n".format(pkg['name']))
|
|
|
|
pkg['license'] = []
|
2016-11-16 01:41:43 -05:00
|
|
|
license_raw = input(("\nWhat is {0}'s license(s)? (See https://wiki.archlinux.org/index.php/PKGBUILD#license)\n" +
|
|
|
|
"If you have more than one, separate them by spaces.\n").format(pkg['name'])).upper()
|
2016-11-12 12:05:03 -05:00
|
|
|
pkg['license'] = list(map(str, license_raw.split()))
|
|
|
|
pkg['deps'] = []
|
2016-11-16 01:41:43 -05:00
|
|
|
deps_raw = input(("\nWhat does {0} depend on for runtime? if no packages, just hit enter.\n" +
|
2016-11-13 17:50:44 -05:00
|
|
|
"Make sure they correspond to Arch/AUR package names.\n" +
|
2016-11-16 01:41:43 -05:00
|
|
|
"If you have more than one, separate them by spaces.\n").format(pkg['name']))
|
2016-11-13 17:31:08 -05:00
|
|
|
if deps_raw:
|
|
|
|
pkg['deps'] = list(map(str, deps_raw.split()))
|
2016-11-12 12:05:03 -05:00
|
|
|
pkg['optdeps'] = []
|
2016-11-16 01:41:43 -05:00
|
|
|
optdeps_raw = input(("\nWhat does {0} optionally depend on (runtime)? if no packages, just hit enter.\n" +
|
2016-11-13 17:50:44 -05:00
|
|
|
"Make sure they correspond to Arch/AUR package names.\n" +
|
|
|
|
"If you have more than one, separate them by COMMAS.\n" +
|
|
|
|
"They should follow this format:\n" +
|
2016-11-16 01:41:43 -05:00
|
|
|
"pkgname: some reason why it should be installed\n").format(pkg['name']))
|
2016-11-13 17:31:08 -05:00
|
|
|
if optdeps_raw:
|
|
|
|
pkg['optdeps'] = list(map(str, optdeps_raw.split(',')))
|
2016-11-12 12:05:03 -05:00
|
|
|
pkg['makedeps'] = []
|
2016-11-16 01:41:43 -05:00
|
|
|
makedeps_raw = input(("\nWhat dependencies are required for building/making {0}? If no packages, just hit enter.\n" +
|
2016-11-13 17:50:44 -05:00
|
|
|
"Make sure they correspond to Arch/AUR package names.\n" +
|
2016-11-16 01:41:43 -05:00
|
|
|
"If you have more than one, separate them by spaces.\n").format(pkg['name']))
|
|
|
|
if makedeps_raw:
|
2016-11-13 17:31:08 -05:00
|
|
|
pkg['makedeps'] = list(map(str, makedeps_raw.split()))
|
|
|
|
if pkg['type'] == 'vcs':
|
|
|
|
pkg['provides'] = [pkg['name'].split('-')[0]]
|
|
|
|
else:
|
|
|
|
pkg['provides'] = [pkg['name']]
|
2016-11-12 12:05:03 -05:00
|
|
|
pkg['provides'] = []
|
2016-11-16 01:41:43 -05:00
|
|
|
provides_raw = input(("\nWhat package names, if any besides itself, does {0} provide?\n" +
|
2016-11-13 17:50:44 -05:00
|
|
|
"(If {0} is a VCS package, do NOT include the non-VCS package name- that's added by default!)\n" +
|
2016-11-16 01:41:43 -05:00
|
|
|
"If you have more than one, separate them by spaces.\n").format(pkg['name']))
|
2016-11-13 17:31:08 -05:00
|
|
|
if provides_raw:
|
|
|
|
provides_list = list(map(str, provides_raw.split()))
|
|
|
|
pkg['provides'] = pkg['provides'] + provides_list
|
|
|
|
if pkg['type'] == 'vcs':
|
|
|
|
pkg['conflicts'] = [pkg['name'].split('-')[0]]
|
|
|
|
else:
|
|
|
|
pkg['conflicts'] = [pkg['name']]
|
2016-11-16 01:41:43 -05:00
|
|
|
conflicts_raw = input(("\nWhat package names, if any, does {0} conflict with?\n" +
|
2016-11-13 17:50:44 -05:00
|
|
|
"(If {0} is a VCS package, do NOT include the non-VCS package name- that's added by default!)\n" +
|
2016-11-16 01:41:43 -05:00
|
|
|
"If you have more than one, separate them by spaces.\n").format(pkg['name']))
|
2016-11-13 17:31:08 -05:00
|
|
|
if conflicts_raw:
|
|
|
|
conflicts_list = list(map(str, conflicts_raw.split()))
|
|
|
|
pkg['conflicts'] = pkg['conflicts'] + conflicts_list
|
2016-11-12 12:05:03 -05:00
|
|
|
return(pkg)
|
|
|
|
|
2016-11-12 16:44:26 -05:00
|
|
|
## MAKE SURE SOME PREREQS HAPPEN ##
|
2016-11-16 01:41:43 -05:00
|
|
|
def sanity_checks():
|
|
|
|
os.makedirs(pkgbuild_dir, exist_ok = True)
|
2016-11-12 16:44:26 -05:00
|
|
|
|
2016-11-13 16:09:16 -05:00
|
|
|
## REGISTER IN THE AUR AND MAKE FIRST COMMIT ##
|
2016-11-12 16:44:26 -05:00
|
|
|
def aur_create(pkg):
|
2016-11-13 16:09:16 -05:00
|
|
|
# git clone from AUR to create repository, add .gitignore
|
|
|
|
tmpcheckout = os.path.join(gettempdir(), '.aur_pkgs')
|
|
|
|
repo_dir = tmpcheckout + '/' + pkg['name']
|
2016-11-16 01:41:43 -05:00
|
|
|
if os.path.exists(repo_dir):
|
|
|
|
shutil.rmtree(repo_dir)
|
|
|
|
git_repo_dir = git.osp.join(tmpcheckout, pkg['name'])
|
|
|
|
# oops. originally had branch='master', which causes it to die since we're cloneing an empty repo
|
|
|
|
aur_repo = git.Repo.clone_from('aur@aur.archlinux.org:' + pkg['name'], git_repo_dir)
|
|
|
|
repo_dir = aur_repo.working_tree_dir
|
|
|
|
shutil.copy2(aur_pkgs_dir + "/_docs/PKGBUILD.templates.d/gitignore", repo_dir + "/.gitignore")
|
|
|
|
aur_repo.index.add(['.gitignore'])
|
2016-11-13 16:09:16 -05:00
|
|
|
# Create the initial PKGBUILD
|
|
|
|
tpl_dir = aur_pkgs_dir + '/_docs/PKGBUILD.templates.d.python'
|
|
|
|
tpl_meta = aur_pkgs_dir + '/_docs/PKGBUILD.templates.d.python' + '/' + pkg['type'] + '.all'
|
|
|
|
tpl_fsloader = jinja2.FileSystemLoader(searchpath = tpl_dir, followlinks = True)
|
|
|
|
tpl_fsloader2 = jinja2.FileSystemLoader(searchpath = tpl_dir + '/' + pkg['type'], followlinks = True)
|
|
|
|
pkgbuild_list = tpl_fsloader2.list_templates()
|
|
|
|
pkgbuild_list[:] = [pkg['type'] + '/' + s for s in pkgbuild_list]
|
|
|
|
tpl_env = jinja2.Environment(loader = tpl_fsloader)
|
2016-11-13 17:50:44 -05:00
|
|
|
pkgbuild_out = tpl_env.get_template(pkg['type'] + '.all.j2').render(pkg = pkg,
|
|
|
|
maintname = maintname,
|
|
|
|
gpgkey = gpgkey,
|
|
|
|
pkgbuild_list = pkgbuild_list)
|
2016-11-16 01:41:43 -05:00
|
|
|
with open(repo_dir + "/PKGBUILD", "w+") as pkgbuild_file:
|
2016-11-13 16:09:16 -05:00
|
|
|
pkgbuild_file.write(pkgbuild_out)
|
|
|
|
# Move the source file
|
|
|
|
if pkg['srcfile']:
|
2016-11-16 01:41:43 -05:00
|
|
|
# sign with GPG
|
2016-11-13 16:09:16 -05:00
|
|
|
gpg = gnupg.GPG()
|
2016-11-16 01:41:43 -05:00
|
|
|
datastream = open(pkg['src_dl'], 'rb')
|
|
|
|
print("If you see an error about 'KEY_CONSIDERED', you most likely can ignore it.")
|
|
|
|
gpg.sign_file(datastream, keyid = gpgkey[0], detach = True, clearsign = False, output = repo_dir + '/' + pkg['srcfile'] + '.sig')
|
2016-11-13 16:09:16 -05:00
|
|
|
datastream.close()
|
2016-11-16 01:41:43 -05:00
|
|
|
aur_repo.index.add([pkg['srcfile'] + '.sig'])
|
|
|
|
aur_repo.index.add(['PKGBUILD'])
|
2016-11-13 17:31:08 -05:00
|
|
|
# TODO: SRCINFO!
|
|
|
|
now = datetime.datetime.utcnow().strftime("%a %b %d %H:%M:%S UTC %Y")
|
|
|
|
srcinfo_out = tpl_env.get_template('srcinfo.j2').render(pkg = pkg, now = now )
|
2016-11-16 01:41:43 -05:00
|
|
|
with open(repo_dir + "/.SRCINFO", "w+") as srcinfo_file:
|
2016-11-13 17:31:08 -05:00
|
|
|
srcinfo_file.write(srcinfo_out)
|
2016-11-16 01:41:43 -05:00
|
|
|
aur_repo.index.add(['.SRCINFO'])
|
2016-11-13 16:09:16 -05:00
|
|
|
# commit to git
|
|
|
|
aur_repo.index.commit("initial commit; setting up .gitignores and skeleton PKGBUILD")
|
|
|
|
# and push...
|
2016-11-16 01:56:07 -05:00
|
|
|
aur_repo.remotes.origin.push()
|
2016-11-16 01:41:43 -05:00
|
|
|
# and delete the repo and source file
|
2016-11-21 01:10:18 -05:00
|
|
|
#shutil.rmtree(repo_dir)
|
2016-11-16 01:41:43 -05:00
|
|
|
if pkg['srcfile']:
|
|
|
|
os.remove(pkg['src_dl'])
|
2016-11-12 16:44:26 -05:00
|
|
|
|
2016-11-13 17:31:08 -05:00
|
|
|
## ADD THE SUBMODULE TO THE MAIN AUR TREE ##
|
|
|
|
def aur_submodule(pkg):
|
2016-11-13 18:44:59 -05:00
|
|
|
aur_pkgs_repo = git.Repo(aur_pkgs_dir)
|
2016-11-16 02:28:42 -05:00
|
|
|
aur_pkgs_repo.create_submodule(pkg['name'], aur_pkgs_dir + '/' + pkg['name'], url='aur@aur.archlinux.org:' + pkg['name'])
|
2016-11-13 18:44:59 -05:00
|
|
|
aur_pkgs_repo.index.commit("adding {0}".format(pkg['name']))
|
|
|
|
# Comment me out if you don't have access to upstream:
|
2016-11-16 01:56:07 -05:00
|
|
|
aur_pkgs_repo.remotes.origin.push()
|
2016-11-13 18:44:59 -05:00
|
|
|
# And don't forget to add the hook to make life easier for us in the future.
|
|
|
|
# WARNING: HERE BE POSSIBLE RACE CONDITIONS
|
|
|
|
mod_gitdir = aur_pkgs_repo.submodules[-1].module().git_dir
|
|
|
|
if not aur_pkgs_repo.submodules[-1].module().git_dir.endswith(pkg['name']):
|
|
|
|
exit('SOMETHING FUCKED UP BADLY. Do not run this thing in parallel.')
|
|
|
|
else:
|
|
|
|
pass
|
|
|
|
shutil.copy2(aur_pkgs_dir + '/_docs/PKGBUILD.templates.d.python/pre-commit.hook.sh', mod_gitdir + '/hooks/pre-commit', follow_symlinks = True)
|
2016-11-13 17:31:08 -05:00
|
|
|
print()
|
2016-11-12 16:44:26 -05:00
|
|
|
|
2016-11-13 17:50:44 -05:00
|
|
|
#pprint.pprint(gui_init())
|
2016-11-12 12:05:03 -05:00
|
|
|
|
2016-11-13 16:09:16 -05:00
|
|
|
#aur_create(gui_init())
|
2016-11-16 01:41:43 -05:00
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
pkg = gui_init()
|
|
|
|
sanity_checks()
|
|
|
|
aur_create(pkg)
|
|
|
|
aur_submodule(pkg)
|
2016-11-16 02:45:07 -05:00
|
|
|
print("Done! Successfully added {0} to the AUR and your local AUR meta-repository.".format(pkg['name']))
|