Last active
October 16, 2019 08:59
-
-
Save mauritsvanrees/c47e974e418c707a626200cb6561405b to your computer and use it in GitHub Desktop.
Parse buildout config into constraints and requirements. See https://community.plone.org/t/creating-constraints-and-requirements-from-buildout-config/10296
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
# https://gist.github.com/mauritsvanrees/c47e974e418c707a626200cb6561405b | |
# https://community.plone.org/t/creating-constraints-and-requirements-from-buildout-config/10296 | |
# from configparser import ConfigParser | |
from zc.buildout import buildout | |
import re | |
# We read this buildout config file: | |
configfile = "buildout.cfg" | |
# We cannot use configparser, because buildout configs contain 'duplicate' options: | |
# $ grep -i chameleon versions.cfg | |
# Chameleon = 3.6.2 | |
# chameleon = 3.6.2 | |
# So we use the buildout configparser: | |
config = buildout.Buildout(configfile, []) | |
# Get the constraints from the version pins. | |
# Ah, the versions get set directly on the config, nice! | |
# Note: this works like a dictionary, but is a class 'zc.buildout.buildout.Options'. | |
versions = config.versions | |
constraints_file = "parsed-constraints.txt" | |
with open(constraints_file, "w") as cfile: | |
cfile.write("# Constraints parsed from {}\n".format(configfile)) | |
for package, version in sorted(versions.items()): | |
cfile.write("{}=={}\n".format(package, version)) | |
print("Wrote versions as constraints to {}.".format(constraints_file)) | |
# Now for the requirements. | |
# We must avoid automatically downloading eggs, so we use the raw config. | |
config = config._raw | |
reqs = set(["zc.buildout"]) | |
extensions = config["buildout"].get("extensions", "").split() | |
reqs.update(set(extensions)) | |
# Regular expression for ${test:eggs}. | |
# Probably needs some tweaks, but works for now. | |
re_option_reference = re.compile(r"\${(.*):(.*)}") | |
def get_option_reference(text): | |
# When text is ${test:eggs}, return ("test", "eggs"). | |
match = re_option_reference.match(text) | |
if match is None: | |
return | |
return match.groups() | |
def read_eggs(section, option="eggs"): | |
# Get the eggs. Tricky is that these can be split on one line or on multiple lines. | |
# And there can be extras, either "package[extra]" or "package [extra]", | |
# making splitting harder. | |
result = set() | |
for line in config[section].get(option, "").splitlines(): | |
line = line.strip() | |
if not line: | |
continue | |
# Get rid of spaces in front of an [extra]. | |
line = "[".join([part.strip() for part in line.split("[")]) | |
for egg in line.split(): | |
# Because we use the raw buildout config, we may have eggs like this: | |
# ${test:eggs} | |
ref = get_option_reference(egg) | |
if not ref: | |
result.add(egg) | |
continue | |
other_section, other_option = ref | |
if other_section == ".": | |
other_section = section | |
eggs = read_eggs(other_section, other_option) | |
result.update(eggs) | |
return result | |
for section in config.keys(): | |
data = config[section] | |
recipe = data.get("recipe") | |
if recipe: | |
reqs.add(recipe) | |
# Get the eggs options. | |
reqs.update(read_eggs(section)) | |
requirements_file = "parsed-requirements.txt" | |
with open(requirements_file, "w") as rfile: | |
rfile.write("# Requirements parsed from {}\n".format(configfile)) | |
for package in sorted(reqs): | |
rfile.write("{}\n".format(package)) | |
print("Wrote requirements to {}.".format(requirements_file)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment