Skip to content

Instantly share code, notes, and snippets.

@homebysix
Last active July 5, 2021 00:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save homebysix/0c42be278696717f28a56c819fc4fddb to your computer and use it in GitHub Desktop.
Save homebysix/0c42be278696717f28a56c819fc4fddb to your computer and use it in GitHub Desktop.
Convert AutoPkg recipes to AppPkgCreator
#!/usr/local/autopkg/python
# encoding: utf-8
# PkgCreator to AppPkgCreator
# Copyright 2019-2021 Elliot Jordan
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""PkgCreator_to_AppPkgCreator.py
Script for converting compatible AutoPkg "pkg" type recipes from using
PkgRootCreator-Copier-PkgCreator to using AppPkgCreator.
"""
import argparse
import os
import plistlib
from distutils.version import LooseVersion
from xml.parsers.expat import ExpatError
def build_argument_parser():
"""Build and return the argument parser."""
parser = argparse.ArgumentParser(
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument("repo_path", help="Path to search for AutoPkg recipes.")
parser.add_argument(
"--auto",
action="store_true",
default=False,
help="Automatically apply suggested changes to recipes. Only recommended "
"for repos you manage or are submitting a pull request to. (Applying "
"changes to repos added using `autopkg repo-add` may result in failure "
"to update the repo in the future.)",
)
parser.add_argument(
"-v",
"--verbose",
action="count",
default=0,
help="Print additional output useful for "
"troubleshooting. (Can be specified multiple times.)",
)
return parser
def evaluate(recipe, recipe_path, args):
"""Perform conversion to AppPkgCreator."""
if args.verbose > 0:
print("Evaluating %s..." % (recipe_path))
skip_marker = "Cannot use AppPkgCreator"
if skip_marker in recipe.get("Description", "") + recipe.get("Comment", ""):
if args.verbose > 1:
print(
" Skipping: A comment indicates we can't use AppPkgCreator for this recipe."
)
return
if "Process" not in recipe:
if args.verbose > 1:
print(" Skipping: This recipe has no process")
return
process = recipe.get("Process")
if len(process) < 3:
if args.verbose > 1:
print(
" Skipping: This recipe doesn't use PkgRootCreator-Copier-PkgCreator."
)
return
if "AppPkgCreator" in [x["Processor"] for x in process]:
if args.verbose > 1:
print(" Skipping: This recipe already uses AppPkgCreator.")
return
old_procs = {"PkgRootCreator": -1, "Copier": -1, "PkgCreator": -1}
for old_proc in old_procs:
for idx, processor in enumerate([x["Processor"] for x in process]):
if processor == old_proc:
old_procs[old_proc] = idx
indexes = (
old_procs["PkgRootCreator"],
old_procs["Copier"],
old_procs["PkgCreator"],
)
if sorted(indexes) != list(range(min(indexes), max(indexes) + 1)):
if args.verbose > 1:
print(
" Skipping: PkgRootCreator-Copier-PkgCreator not found in sequential order."
)
return
pkgrootcreator_model_args = {
"pkgdirs": {"Applications": "0775"},
"pkgroot": "%RECIPE_CACHE_DIR%/%NAME%",
}
if process[indexes[0]]["Arguments"] != pkgrootcreator_model_args:
if args.verbose > 1:
print(" Skipping: PkgRootCreator arguments are non-standard.")
return
copier_source_path = os.path.split(
recipe["Process"][indexes[1]]["Arguments"]["source_path"]
)
if copier_source_path[0] != "%pathname%" or not copier_source_path[1].endswith(
".app"
):
if args.verbose > 1:
print(" Skipping: Copier arguments are non-standard.")
# TODO: Might be able to use Copier source_path as the AppPkgCreator app_path in some cases.
return
if "scripts" in process[indexes[2]]["Arguments"]["pkg_request"]:
if args.verbose > 1:
print(
" Skipping: PkgCreator includes scripts, which AppPkgCreator does not support."
)
return
if not args.auto:
print("✨ Recipe %s is eligible for AppPkgCreator." % recipe_path)
else:
print("✨ Recipe %s is eligible for AppPkgCreator. Converting..." % recipe_path)
# Remove PkgRootCreator
del recipe["Process"][indexes[0]]
# Remove Copier, which is now at the PkgRootCreator index.
del recipe["Process"][indexes[0]]
# Remove PkgCreator, which is now at the PkgRootCreator index.
del recipe["Process"][indexes[0]]
# Insert AppPkgCreator.
recipe["Process"].insert(indexes[0], {"Processor": "AppPkgCreator"})
# Update minimum AutoPkg version.
if recipe.get("MinimumVersion", "0") < LooseVersion("1.0.0"):
recipe["MinimumVersion"] = "1.0.0"
with open(recipe_path, "wb") as openfile:
plistlib.dump(recipe, openfile)
def main():
"""Main process."""
# Parse command line arguments.
argparser = build_argument_parser()
args = argparser.parse_args()
# Extensions to include in converting.
target_file_exts = (".pkg.recipe",)
# Gather list of eligible files.
for root, dirs, files in os.walk(os.path.expanduser(args.repo_path)):
dirs[:] = [d for d in dirs if not d.startswith(".")]
for idx, filename in enumerate(files):
if filename.endswith(target_file_exts):
recipe_path = os.path.join(root, filename)
try:
with open(recipe_path, "rb") as openfile:
recipe = plistlib.load(openfile)
evaluate(recipe, recipe_path, args)
except ExpatError:
print("[ERROR] Unable to read %s", recipe_path)
continue
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment