ci: update SMJobBlessUtil.py from https://gist.github.com/mikeyh/89a1e2ecc6849ff6056b7391c5216799
This commit is contained in:
parent
dd5143bea8
commit
5d83896080
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@ -49,7 +49,7 @@ jobs:
|
||||
cd ..
|
||||
bundle exec fastlane build
|
||||
echo "Checking SMJobBless Vailded"
|
||||
python SMJobBlessUtil.py check ClashX.app
|
||||
python3 SMJobBlessUtil.py check ClashX.app
|
||||
echo "Check done"
|
||||
|
||||
- name: setup node
|
||||
|
@ -1,20 +1,20 @@
|
||||
#! /usr/bin/python
|
||||
#
|
||||
#! /usr/bin/python3
|
||||
#
|
||||
# File: SMJobBlessUtil.py
|
||||
#
|
||||
#
|
||||
# Contains: Tool for checking and correcting apps that use SMJobBless.
|
||||
#
|
||||
#
|
||||
# Written by: DTS
|
||||
#
|
||||
#
|
||||
# Copyright: Copyright (c) 2012 Apple Inc. All Rights Reserved.
|
||||
#
|
||||
#
|
||||
# Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc.
|
||||
# ("Apple") in consideration of your agreement to the following
|
||||
# terms, and your use, installation, modification or
|
||||
# redistribution of this Apple software constitutes acceptance of
|
||||
# these terms. If you do not agree with these terms, please do
|
||||
# not use, install, modify or redistribute this Apple software.
|
||||
#
|
||||
#
|
||||
# In consideration of your agreement to abide by the following
|
||||
# terms, and subject to these terms, Apple grants you a personal,
|
||||
# non-exclusive license, under Apple's copyrights in this
|
||||
@ -32,14 +32,14 @@
|
||||
# are granted by Apple herein, including but not limited to any
|
||||
# patent rights that may be infringed by your derivative works or
|
||||
# by other works in which the Apple Software may be incorporated.
|
||||
#
|
||||
# The Apple Software is provided by Apple on an "AS IS" basis.
|
||||
#
|
||||
# The Apple Software is provided by Apple on an "AS IS" basis.
|
||||
# APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
|
||||
# WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING
|
||||
# THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
|
||||
# COMBINATION WITH YOUR PRODUCTS.
|
||||
#
|
||||
#
|
||||
# IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT,
|
||||
# INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
@ -49,7 +49,7 @@
|
||||
# OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR
|
||||
# OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
# SUCH DAMAGE.
|
||||
#
|
||||
#
|
||||
|
||||
import sys
|
||||
import os
|
||||
@ -57,17 +57,18 @@ import getopt
|
||||
import subprocess
|
||||
import plistlib
|
||||
import operator
|
||||
import platform
|
||||
|
||||
class UsageException (Exception):
|
||||
"""
|
||||
Raised when the progam detects a usage issue; the top-level code catches this
|
||||
Raised when the progam detects a usage issue; the top-level code catches this
|
||||
and prints a usage message.
|
||||
"""
|
||||
pass
|
||||
|
||||
class CheckException (Exception):
|
||||
"""
|
||||
Raised when the "check" subcommand detects a problem; the top-level code catches
|
||||
Raised when the "check" subcommand detects a problem; the top-level code catches
|
||||
this and prints a nice error message.
|
||||
"""
|
||||
def __init__(self, message, path=None):
|
||||
@ -77,38 +78,38 @@ class CheckException (Exception):
|
||||
def checkCodeSignature(programPath, programType):
|
||||
"""Checks the code signature of the referenced program."""
|
||||
|
||||
# Use the codesign tool to check the signature. The second "-v" is required to enable
|
||||
# verbose mode, which causes codesign to do more checking. By default it does the minimum
|
||||
# amount of checking ("Is the program properly signed?"). If you enabled verbose mode it
|
||||
# does other sanity checks, which we definitely want. The specific thing I'd like to
|
||||
# detect is "Does the code satisfy its own designated requirement?" and I need to enable
|
||||
# Use the codesign tool to check the signature. The second "-v" is required to enable
|
||||
# verbose mode, which causes codesign to do more checking. By default it does the minimum
|
||||
# amount of checking ("Is the program properly signed?"). If you enabled verbose mode it
|
||||
# does other sanity checks, which we definitely want. The specific thing I'd like to
|
||||
# detect is "Does the code satisfy its own designated requirement?" and I need to enable
|
||||
# verbose mode to get that.
|
||||
|
||||
args = [
|
||||
# "false",
|
||||
"codesign",
|
||||
"-v",
|
||||
# "false",
|
||||
"codesign",
|
||||
"-v",
|
||||
"-v",
|
||||
programPath
|
||||
]
|
||||
try:
|
||||
subprocess.check_call(args, stderr=open("/dev/null"))
|
||||
except subprocess.CalledProcessError, e:
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise CheckException("%s code signature invalid" % programType, programPath)
|
||||
|
||||
|
||||
def readDesignatedRequirement(programPath, programType):
|
||||
"""Returns the designated requirement of the program as a string."""
|
||||
args = [
|
||||
# "false",
|
||||
"codesign",
|
||||
"-d",
|
||||
"-r",
|
||||
"-",
|
||||
# "false",
|
||||
"codesign",
|
||||
"-d",
|
||||
"-r",
|
||||
"-",
|
||||
programPath
|
||||
]
|
||||
try:
|
||||
req = subprocess.check_output(args, stderr=open("/dev/null"))
|
||||
except subprocess.CalledProcessError, e:
|
||||
req = subprocess.check_output(args, stderr=open("/dev/null"), encoding="utf-8")
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise CheckException("%s designated requirement unreadable" % programType, programPath)
|
||||
|
||||
reqLines = req.splitlines()
|
||||
@ -119,77 +120,97 @@ def readDesignatedRequirement(programPath, programType):
|
||||
def readInfoPlistFromPath(infoPath):
|
||||
"""Reads an "Info.plist" file from the specified path."""
|
||||
try:
|
||||
info = plistlib.readPlist(infoPath)
|
||||
with open(infoPath, 'rb') as fp:
|
||||
info = plistlib.load(fp)
|
||||
except:
|
||||
raise CheckException("'Info.plist' not readable", infoPath)
|
||||
if not isinstance(info, dict):
|
||||
raise CheckException("'Info.plist' root must be a dictionary", infoPath)
|
||||
return info
|
||||
|
||||
|
||||
def readPlistFromToolSection(toolPath, segmentName, sectionName):
|
||||
"""Reads a dictionary property list from the specified section within the specified executable."""
|
||||
|
||||
|
||||
# Run otool -s to get a hex dump of the section.
|
||||
|
||||
|
||||
args = [
|
||||
# "false",
|
||||
"otool",
|
||||
"-s",
|
||||
segmentName,
|
||||
sectionName,
|
||||
# "false",
|
||||
"otool",
|
||||
"-V",
|
||||
"-arch",
|
||||
platform.machine(),
|
||||
"-s",
|
||||
segmentName,
|
||||
sectionName,
|
||||
toolPath
|
||||
]
|
||||
try:
|
||||
plistDump = subprocess.check_output(args)
|
||||
except subprocess.CalledProcessError, e:
|
||||
plistDump = subprocess.check_output(args, encoding="utf-8")
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise CheckException("tool %s / %s section unreadable" % (segmentName, sectionName), toolPath)
|
||||
|
||||
# Convert that hex dump to an property list.
|
||||
|
||||
plistLines = plistDump.splitlines()
|
||||
if len(plistLines) < 3 or plistLines[1] != ("Contents of (%s,%s) section" % (segmentName, sectionName)):
|
||||
# Convert that dump to an property list.
|
||||
|
||||
plistLines = plistDump.strip().splitlines(keepends=True)
|
||||
|
||||
if len(plistLines) < 3:
|
||||
raise CheckException("tool %s / %s section dump malformed (1)" % (segmentName, sectionName), toolPath)
|
||||
|
||||
header = plistLines[1].strip()
|
||||
|
||||
if not header.endswith("(%s,%s) section" % (segmentName, sectionName)):
|
||||
raise CheckException("tool %s / %s section dump malformed (2)" % (segmentName, sectionName), toolPath)
|
||||
|
||||
del plistLines[0:2]
|
||||
|
||||
try:
|
||||
bytes = []
|
||||
for line in plistLines:
|
||||
# line looks like this:
|
||||
#
|
||||
# '0000000100000b80\t3c 3f 78 6d 6c 20 76 65 72 73 69 6f 6e 3d 22 31 '
|
||||
columns = line.split("\t")
|
||||
assert len(columns) == 2
|
||||
for hexStr in columns[1].split():
|
||||
bytes.append(int(hexStr, 16))
|
||||
plist = plistlib.readPlistFromString(bytearray(bytes))
|
||||
|
||||
if header.startswith('Contents of'):
|
||||
data = []
|
||||
for line in plistLines:
|
||||
# line looks like this:
|
||||
#
|
||||
# '100000000 3c 3f 78 6d 6c 20 76 65 72 73 69 6f 6e 3d 22 31 |<?xml version="1|'
|
||||
parts = line.split('|')
|
||||
assert len(parts) == 3
|
||||
columns = parts[0].split()
|
||||
assert len(columns) >= 2
|
||||
del columns[0]
|
||||
for hexStr in columns:
|
||||
data.append(int(hexStr, 16))
|
||||
data = bytes(data)
|
||||
else:
|
||||
data = bytes("".join(plistLines), encoding="utf-8")
|
||||
|
||||
plist = plistlib.loads(data)
|
||||
except:
|
||||
raise CheckException("tool %s / %s section dump malformed (2)" % (segmentName, sectionName), toolPath)
|
||||
raise CheckException("tool %s / %s section dump malformed (3)" % (segmentName, sectionName), toolPath)
|
||||
|
||||
# Check the root of the property list.
|
||||
|
||||
|
||||
if not isinstance(plist, dict):
|
||||
raise CheckException("tool %s / %s property list root must be a dictionary" % (segmentName, sectionName), toolPath)
|
||||
|
||||
return plist
|
||||
|
||||
|
||||
def checkStep1(appPath):
|
||||
"""Checks that the app and the tool are both correctly code signed."""
|
||||
|
||||
|
||||
if not os.path.isdir(appPath):
|
||||
raise CheckException("app not found", appPath)
|
||||
|
||||
|
||||
# Check the app's code signature.
|
||||
|
||||
|
||||
checkCodeSignature(appPath, "app")
|
||||
|
||||
|
||||
# Check the tool directory.
|
||||
|
||||
|
||||
toolDirPath = os.path.join(appPath, "Contents", "Library", "LaunchServices")
|
||||
if not os.path.isdir(toolDirPath):
|
||||
raise CheckException("tool directory not found", toolDirPath)
|
||||
|
||||
# Check each tool's code signature.
|
||||
|
||||
|
||||
toolPathList = []
|
||||
for toolName in os.listdir(toolDirPath):
|
||||
if toolName != ".DS_Store":
|
||||
@ -200,50 +221,50 @@ def checkStep1(appPath):
|
||||
toolPathList.append(toolPath)
|
||||
|
||||
# Check that we have at least one tool.
|
||||
|
||||
|
||||
if len(toolPathList) == 0:
|
||||
raise CheckException("no tools found", toolDirPath)
|
||||
|
||||
return toolPathList
|
||||
|
||||
|
||||
def checkStep2(appPath, toolPathList):
|
||||
"""Checks the SMPrivilegedExecutables entry in the app's "Info.plist"."""
|
||||
|
||||
# Create a map from the tool name (not path) to its designated requirement.
|
||||
|
||||
|
||||
toolNameToReqMap = dict()
|
||||
for toolPath in toolPathList:
|
||||
req = readDesignatedRequirement(toolPath, "tool")
|
||||
toolNameToReqMap[os.path.basename(toolPath)] = req
|
||||
|
||||
|
||||
# Read the Info.plist for the app and extract the SMPrivilegedExecutables value.
|
||||
|
||||
|
||||
infoPath = os.path.join(appPath, "Contents", "Info.plist")
|
||||
info = readInfoPlistFromPath(infoPath)
|
||||
if not info.has_key("SMPrivilegedExecutables"):
|
||||
if "SMPrivilegedExecutables" not in info:
|
||||
raise CheckException("'SMPrivilegedExecutables' not found", infoPath)
|
||||
infoToolDict = info["SMPrivilegedExecutables"]
|
||||
if not isinstance(infoToolDict, dict):
|
||||
raise CheckException("'SMPrivilegedExecutables' must be a dictionary", infoPath)
|
||||
|
||||
|
||||
# Check that the list of tools matches the list of SMPrivilegedExecutables entries.
|
||||
|
||||
|
||||
if sorted(infoToolDict.keys()) != sorted(toolNameToReqMap.keys()):
|
||||
raise CheckException("'SMPrivilegedExecutables' and tools in 'Contents/Library/LaunchServices' don't match")
|
||||
|
||||
|
||||
# Check that all the requirements match.
|
||||
|
||||
# This is an interesting policy choice. Technically the tool just needs to match
|
||||
# the requirement listed in SMPrivilegedExecutables, and we can check that by
|
||||
|
||||
# This is an interesting policy choice. Technically the tool just needs to match
|
||||
# the requirement listed in SMPrivilegedExecutables, and we can check that by
|
||||
# putting the requirement into tmp.req and then running
|
||||
#
|
||||
# $ codesign -v -R tmp.req /path/to/tool
|
||||
#
|
||||
# However, for a Developer ID signed tool we really want to have the SMPrivilegedExecutables
|
||||
# entry contain the tool's designated requirement because Xcode has built a
|
||||
# more complex DR that does lots of useful and important checks. So, as a matter
|
||||
# However, for a Developer ID signed tool we really want to have the SMPrivilegedExecutables
|
||||
# entry contain the tool's designated requirement because Xcode has built a
|
||||
# more complex DR that does lots of useful and important checks. So, as a matter
|
||||
# of policy we require that the value in SMPrivilegedExecutables match the tool's DR.
|
||||
|
||||
|
||||
for toolName in infoToolDict:
|
||||
if infoToolDict[toolName] != toolNameToReqMap[toolName]:
|
||||
raise CheckException("tool designated requirement (%s) doesn't match entry in 'SMPrivilegedExecutables' (%s)" % (toolNameToReqMap[toolName], infoToolDict[toolName]))
|
||||
@ -252,29 +273,29 @@ def checkStep3(appPath, toolPathList):
|
||||
"""Checks the "Info.plist" embedded in each helper tool."""
|
||||
|
||||
# First get the app's designated requirement.
|
||||
|
||||
|
||||
appReq = readDesignatedRequirement(appPath, "app")
|
||||
|
||||
# Then check that the tool's SMAuthorizedClients value matches it.
|
||||
|
||||
# Then check that the tool's SMAuthorizedClients value matches it.
|
||||
|
||||
for toolPath in toolPathList:
|
||||
info = readPlistFromToolSection(toolPath, "__TEXT", "__info_plist")
|
||||
|
||||
if not info.has_key("CFBundleInfoDictionaryVersion") or info["CFBundleInfoDictionaryVersion"] != "6.0":
|
||||
if "CFBundleInfoDictionaryVersion" not in info or info["CFBundleInfoDictionaryVersion"] != "6.0":
|
||||
raise CheckException("'CFBundleInfoDictionaryVersion' in tool __TEXT / __info_plist section must be '6.0'", toolPath)
|
||||
|
||||
if not info.has_key("CFBundleIdentifier") or info["CFBundleIdentifier"] != os.path.basename(toolPath):
|
||||
if "CFBundleIdentifier" not in info or info["CFBundleIdentifier"] != os.path.basename(toolPath):
|
||||
raise CheckException("'CFBundleIdentifier' in tool __TEXT / __info_plist section must match tool name", toolPath)
|
||||
|
||||
if not info.has_key("SMAuthorizedClients"):
|
||||
if "SMAuthorizedClients" not in info:
|
||||
raise CheckException("'SMAuthorizedClients' in tool __TEXT / __info_plist section not found", toolPath)
|
||||
infoClientList = info["SMAuthorizedClients"]
|
||||
if not isinstance(infoClientList, list):
|
||||
raise CheckException("'SMAuthorizedClients' in tool __TEXT / __info_plist section must be an array", toolPath)
|
||||
if len(infoClientList) != 1:
|
||||
raise CheckException("'SMAuthorizedClients' in tool __TEXT / __info_plist section must have one entry", toolPath)
|
||||
|
||||
# Again, as a matter of policy we require that the SMAuthorizedClients entry must
|
||||
|
||||
# Again, as a matter of policy we require that the SMAuthorizedClients entry must
|
||||
# match exactly the designated requirement of the app.
|
||||
|
||||
if infoClientList[0] != appReq:
|
||||
@ -286,22 +307,22 @@ def checkStep4(appPath, toolPathList):
|
||||
for toolPath in toolPathList:
|
||||
launchd = readPlistFromToolSection(toolPath, "__TEXT", "__launchd_plist")
|
||||
|
||||
if not launchd.has_key("Label") or launchd["Label"] != os.path.basename(toolPath):
|
||||
if "Label" not in launchd or launchd["Label"] != os.path.basename(toolPath):
|
||||
raise CheckException("'Label' in tool __TEXT / __launchd_plist section must match tool name", toolPath)
|
||||
|
||||
# We don't need to check that the label matches the bundle identifier because
|
||||
# we know it matches the tool name and step 4 checks that the tool name matches
|
||||
# We don't need to check that the label matches the bundle identifier because
|
||||
# we know it matches the tool name and step 4 checks that the tool name matches
|
||||
# the bundle identifier.
|
||||
|
||||
def checkStep5(appPath):
|
||||
"""There's nothing to do here; we effectively checked for this is steps 1 and 2."""
|
||||
pass
|
||||
|
||||
|
||||
def check(appPath):
|
||||
"""Checks the SMJobBless setup of the specified app."""
|
||||
|
||||
# Each of the following steps matches a bullet point in the SMJobBless header doc.
|
||||
|
||||
|
||||
toolPathList = checkStep1(appPath)
|
||||
|
||||
checkStep2(appPath, toolPathList)
|
||||
@ -314,7 +335,7 @@ def check(appPath):
|
||||
|
||||
def setreq(appPath, appInfoPlistPath, toolInfoPlistPaths):
|
||||
"""
|
||||
Reads information from the built app and uses it to set the SMJobBless setup
|
||||
Reads information from the built app and uses it to set the SMJobBless setup
|
||||
in the specified app and tool Info.plist source files.
|
||||
"""
|
||||
|
||||
@ -328,20 +349,16 @@ def setreq(appPath, appInfoPlistPath, toolInfoPlistPaths):
|
||||
raise CheckException("app 'Info.plist' not found", toolInfoPlistPath)
|
||||
|
||||
# Get the designated requirement for the app and each of the tools.
|
||||
|
||||
|
||||
appReq = readDesignatedRequirement(appPath, "app")
|
||||
|
||||
toolDirPath = os.path.join(appPath, "Contents", "Library", "LaunchServices")
|
||||
if not os.path.isdir(toolDirPath):
|
||||
raise CheckException("tool directory not found", toolDirPath)
|
||||
|
||||
|
||||
toolNameToReqMap = {}
|
||||
print os.listdir(toolDirPath)
|
||||
for toolName in os.listdir(toolDirPath):
|
||||
req = readDesignatedRequirement(os.path.join(toolDirPath, toolName), "tool")
|
||||
print '-----'
|
||||
print toolName
|
||||
print '-----'
|
||||
toolNameToReqMap[toolName] = req
|
||||
|
||||
if len(toolNameToReqMap) > len(toolInfoPlistPaths):
|
||||
@ -350,58 +367,60 @@ def setreq(appPath, appInfoPlistPath, toolInfoPlistPaths):
|
||||
raise CheckException("tool directory has fewer tools (%d) than you've supplied tool 'Info.plist' paths (%d)" % (len(toolNameToReqMap), len(toolInfoPlistPaths)), toolDirPath)
|
||||
|
||||
# Build the new value for SMPrivilegedExecutables.
|
||||
|
||||
|
||||
appToolDict = {}
|
||||
toolInfoPlistPathToToolInfoMap = {}
|
||||
for toolInfoPlistPath in toolInfoPlistPaths:
|
||||
toolInfo = readInfoPlistFromPath(toolInfoPlistPath)
|
||||
toolInfoPlistPathToToolInfoMap[toolInfoPlistPath] = toolInfo
|
||||
if not toolInfo.has_key("CFBundleIdentifier"):
|
||||
if "CFBundleIdentifier" not in toolInfo:
|
||||
raise CheckException("'CFBundleIdentifier' not found", toolInfoPlistPath)
|
||||
bundleID = toolInfo["CFBundleIdentifier"]
|
||||
if not isinstance(bundleID, basestring):
|
||||
if not isinstance(bundleID, str):
|
||||
raise CheckException("'CFBundleIdentifier' must be a string", toolInfoPlistPath)
|
||||
appToolDict[bundleID] = toolNameToReqMap[bundleID]
|
||||
|
||||
# Set the SMPrivilegedExecutables value in the app "Info.plist".
|
||||
|
||||
appInfo = readInfoPlistFromPath(appInfoPlistPath)
|
||||
needsUpdate = not appInfo.has_key("SMPrivilegedExecutables")
|
||||
needsUpdate = "SMPrivilegedExecutables" not in appInfo
|
||||
if not needsUpdate:
|
||||
oldAppToolDict = appInfo["SMPrivilegedExecutables"]
|
||||
if not isinstance(oldAppToolDict, dict):
|
||||
raise CheckException("'SMPrivilegedExecutables' must be a dictionary", appInfoPlistPath)
|
||||
appToolDictSorted = sorted(appToolDict.iteritems(), key=operator.itemgetter(0))
|
||||
oldAppToolDictSorted = sorted(oldAppToolDict.iteritems(), key=operator.itemgetter(0))
|
||||
appToolDictSorted = sorted(appToolDict.items(), key=operator.itemgetter(0))
|
||||
oldAppToolDictSorted = sorted(oldAppToolDict.items(), key=operator.itemgetter(0))
|
||||
needsUpdate = (appToolDictSorted != oldAppToolDictSorted)
|
||||
|
||||
|
||||
if needsUpdate:
|
||||
appInfo["SMPrivilegedExecutables"] = appToolDict
|
||||
plistlib.writePlist(appInfo, appInfoPlistPath)
|
||||
print >> sys.stdout, "%s: updated" % appInfoPlistPath
|
||||
|
||||
with open(appInfoPlistPath, 'wb') as fp:
|
||||
plistlib.dump(appInfo, fp)
|
||||
print ("%s: updated" % appInfoPlistPath, file = sys.stdout)
|
||||
|
||||
# Set the SMAuthorizedClients value in each tool's "Info.plist".
|
||||
|
||||
toolAppListSorted = [ appReq ] # only one element, so obviously sorted (-:
|
||||
for toolInfoPlistPath in toolInfoPlistPaths:
|
||||
toolInfo = toolInfoPlistPathToToolInfoMap[toolInfoPlistPath]
|
||||
|
||||
needsUpdate = not toolInfo.has_key("SMAuthorizedClients")
|
||||
|
||||
needsUpdate = "SMAuthorizedClients" not in toolInfo
|
||||
if not needsUpdate:
|
||||
oldToolAppList = toolInfo["SMAuthorizedClients"]
|
||||
if not isinstance(oldToolAppList, list):
|
||||
raise CheckException("'SMAuthorizedClients' must be an array", toolInfoPlistPath)
|
||||
oldToolAppListSorted = sorted(oldToolAppList)
|
||||
needsUpdate = (toolAppListSorted != oldToolAppListSorted)
|
||||
|
||||
|
||||
if needsUpdate:
|
||||
toolInfo["SMAuthorizedClients"] = toolAppListSorted
|
||||
plistlib.writePlist(toolInfo, toolInfoPlistPath)
|
||||
print >> sys.stdout, "%s: updated" % toolInfoPlistPath
|
||||
with open(toolInfoPlistPath, 'wb') as f:
|
||||
plistlib.dump(toolInfo, f)
|
||||
print("%s: updated" % toolInfoPlistPath, file = sys.stdout)
|
||||
|
||||
def main():
|
||||
options, appArgs = getopt.getopt(sys.argv[1:], "d")
|
||||
|
||||
|
||||
debug = False
|
||||
for opt, val in options:
|
||||
if opt == "-d":
|
||||
@ -426,16 +445,16 @@ def main():
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except CheckException, e:
|
||||
except CheckException as e:
|
||||
if e.path is None:
|
||||
print >> sys.stderr, "%s: %s" % (os.path.basename(sys.argv[0]), e.message)
|
||||
print("%s: %s" % (os.path.basename(sys.argv[0]), e.message), file = sys.stderr)
|
||||
else:
|
||||
path = e.path
|
||||
if path.endswith("/"):
|
||||
path = path[:-1]
|
||||
print >> sys.stderr, "%s: %s" % (path, e.message)
|
||||
sys.exit(1)
|
||||
except UsageException, e:
|
||||
print >> sys.stderr, "usage: %s check /path/to/app" % os.path.basename(sys.argv[0])
|
||||
print >> sys.stderr, " %s setreq /path/to/app /path/to/app/Info.plist /path/to/tool/Info.plist..." % os.path.basename(sys.argv[0])
|
||||
print("%s: %s" % (path, e.message), file = sys.stderr)
|
||||
sys.exit(1)
|
||||
except UsageException as e:
|
||||
print("usage: %s check /path/to/app" % os.path.basename(sys.argv[0]), file = sys.stderr)
|
||||
print(" %s setreq /path/to/app /path/to/app/Info.plist /path/to/tool/Info.plist..." % os.path.basename(sys.argv[0]), file = sys.stderr)
|
||||
sys.exit(1)
|
Loading…
Reference in New Issue
Block a user