Last active
July 5, 2021 00:42
-
-
Save homebysix/0c42be278696717f28a56c819fc4fddb to your computer and use it in GitHub Desktop.
Convert AutoPkg recipes to AppPkgCreator
This file contains 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
#!/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