Refactor into Python package format (#1165)

* Refactor into a python module format

-Updates import statements to be absolute vs implicitly relative
-Uses import syntax that minimizes the need to update references in code
and/or reformat affected lines (e.g. `import gapi.directory` becomes `from gam.gapi import directory as
gapi_directory`)
-Adds a `__main__.py` such that the module can be executed on its own
using standard `python3 -m gam` syntax
-Replaces __main__ import hack with module import
-Updates the GAM path to be the module's parent dir

* Add gam.py to /src for backwards compatibility

A stub that calls gam.__main__.main() to be used by users who are not
with the syntax of calling a module implementation. It should also
provide immediate backwards-compatibility with existing scripts with
references to this file.

* Move build tools back to the main dir and out of the package

* Fix pylint errors

* Update build spec to use new package format

Incorporates @jay0lee's patch from
https://github.com/jay0lee/GAM/pull/1165#issuecomment-618430828
This commit is contained in:
ejochman
2020-04-23 11:06:30 -07:00
committed by GitHub
parent 5f6306911f
commit e1660aa909
34 changed files with 11871 additions and 11850 deletions

1
src/.gitignore vendored
View File

@ -64,7 +64,6 @@ nobrowser.txt
nocache.txt
noverifyssl.txt
gamcache/
gam/
gam-64/
*.zip
*.msi

11604
src/gam.py

File diff suppressed because it is too large Load Diff

View File

@ -15,7 +15,7 @@ extra_files += [(os.path.join(proot, 'cacerts.txt'), 'httplib2')]
extra_files += copy_metadata('google-api-python-client')
a = Analysis(['gam.py'],
a = Analysis(['gam/__main__.py'],
hiddenimports=[],
hookspath=None,
excludes=['FixTk', 'tcl', 'tk', '_tkinter', 'tkinter', 'Tkinter'],

11576
src/gam/__init__.py Executable file

File diff suppressed because it is too large Load Diff

47
src/gam/__main__.py Normal file
View File

@ -0,0 +1,47 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# GAM
#
# Copyright 2019, LLC All Rights Reserved.
#
# 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.
"""GAM is a command line tool which allows Administrators to control their G Suite domain and accounts.
With GAM you can programmatically create users, turn on/off services for users like POP and Forwarding and much more.
For more information, see https://git.io/gam
"""
import sys
from multiprocessing import freeze_support
from multiprocessing import set_start_method
from gam import controlflow
import gam
def main(argv):
freeze_support()
if sys.platform == 'darwin':
# https://bugs.python.org/issue33725 in Python 3.8.0 seems
# to break parallel operations with errors about extra -b
# command line arguments
set_start_method('fork')
if sys.version_info[0] < 3 or sys.version_info[1] < 6:
controlflow.system_error_exit(5,
f'GAM requires Python 3.6 or newer. You are running %s.%s.%s. Please upgrade your Python version or use one of the binary GAM downloads.' % sys.version_info[
:3])
sys.exit(gam.ProcessGAMCommand(sys.argv))
# Run from command line
if __name__ == "__main__":
main(sys.argv)

View File

@ -1,10 +1,10 @@
"""Authentication/Credentials general purpose and convenience methods."""
import transport
from var import _FN_OAUTH2_TXT
from var import GC_OAUTH2_TXT
from var import GC_Values
from . import oauth
from gam.auth import oauth
from gam.var import _FN_OAUTH2_TXT
from gam.var import GC_OAUTH2_TXT
from gam.var import GC_Values
# TODO: Move logic that determines file name into this module. We should be able
# to discover the file location without accessing a private member or waiting
# for a global initialization.

View File

@ -12,12 +12,11 @@ import google_auth_oauthlib.flow
import google.oauth2.credentials
import google.oauth2.id_token
import fileutils
import transport
from var import GAM_INFO
from var import GM_Globals
from var import GM_WINDOWS
import utils
from gam import fileutils
from gam import transport
from gam.var import GM_Globals
from gam.var import GM_WINDOWS
from gam import utils
MESSAGE_CONSOLE_AUTHORIZATION_PROMPT = ('\nGo to the following link in your '
'browser:\n\n\t{url}\n')

View File

@ -10,7 +10,7 @@ from unittest.mock import patch
import google.oauth2.credentials
from auth import oauth
from gam.auth import oauth
class CredentialsTest(unittest.TestCase):

View File

@ -3,9 +3,9 @@ import random
import sys
import time
import display # TODO: Change to relative import when gam is setup as a package
from var import MESSAGE_HEADER_NOT_FOUND_IN_CSV_HEADERS
from var import MESSAGE_INVALID_JSON
from gam import display
from gam.var import MESSAGE_HEADER_NOT_FOUND_IN_CSV_HEADERS
from gam.var import MESSAGE_INVALID_JSON
def system_error_exit(return_code, message):
@ -21,38 +21,35 @@ def system_error_exit(return_code, message):
def invalid_argument_exit(argument, command):
'''Indicate that the argument is not valid for the command.
"""Indicate that the argument is not valid for the command.
Args:
argument: the invalid argument
command: the base GAM command
'''
system_error_exit(
2,
f'{argument} is not a valid argument for "{command}"')
"""
system_error_exit(2, f'{argument} is not a valid argument for "{command}"')
def missing_argument_exit(argument, command):
'''Indicate that the argument is missing for the command.
"""Indicate that the argument is missing for the command.
Args:
argument: the missingagrument
command: the base GAM command
'''
system_error_exit(
2,
f'missing argument {argument} for "{command}"')
"""
system_error_exit(2, f'missing argument {argument} for "{command}"')
def expected_argument_exit(name, expected, argument):
'''Indicate that the argument does not have an expected value for the command.
"""Indicate that the argument does not have an expected value for the command.
Args:
name: the field name
expected: the expected values
argument: the invalid argument
'''
system_error_exit(
2,
f'{name} must be one of {expected}; got {argument}')
"""
system_error_exit(2, f'{name} must be one of {expected}; got {argument}')
def csv_field_error_exit(field_name, field_names):
"""Raises a system exit when a CSV field is malformed.

View File

@ -3,7 +3,7 @@
import unittest
from unittest.mock import patch
import controlflow
from gam import controlflow
class ControlFlowTest(unittest.TestCase):

View File

@ -10,10 +10,10 @@ import dateutil
import googleapiclient.http
#TODO: get rid of these hacks
import __main__
from var import *
import controlflow
import gapi
import gam
from gam.var import *
from gam import controlflow
from gam import gapi
def current_count(i, count):
@ -171,8 +171,8 @@ def write_csv_file(csvRows, titles, list_type, todrive):
except IOError as e:
controlflow.system_error_exit(6, e)
if todrive:
admin_email = __main__._getValueFromOAuth('email')
_, drive = __main__.buildDrive3GAPIObject(admin_email)
admin_email = gam._getValueFromOAuth('email')
_, drive = gam.buildDrive3GAPIObject(admin_email)
if not drive:
print(f'''\nGAM is not authorized to create Drive files. Please run:
gam user {admin_email} check serviceaccount
@ -200,7 +200,7 @@ and follow recommend steps to authorize GAM for Drive access.''')
if GC_Values[GC_NO_BROWSER]:
msg_txt = f'Drive file uploaded to:\n {file_url}'
msg_subj = f'{GC_Values[GC_DOMAIN]} - {list_type}'
__main__.send_email(msg_subj, msg_txt)
gam.send_email(msg_subj, msg_txt)
print(msg_txt)
else:
webbrowser.open(file_url)

View File

@ -3,9 +3,9 @@
import unittest
from unittest.mock import patch
import display
from var import ERROR_PREFIX
from var import WARNING_PREFIX
from gam import display
from gam.var import ERROR_PREFIX
from gam.var import WARNING_PREFIX
class DisplayTest(unittest.TestCase):

View File

@ -4,11 +4,11 @@ import io
import os
import sys
import controlflow
import display
from var import GM_Globals
from var import GM_SYS_ENCODING
from var import UTF8_SIG
from gam import controlflow
from gam import display
from gam.var import GM_Globals
from gam.var import GM_SYS_ENCODING
from gam.var import UTF8_SIG
def _open_file(filename, mode, encoding=None, newline=None):

View File

@ -6,7 +6,7 @@ import unittest
from unittest.mock import MagicMock
from unittest.mock import patch
import fileutils
from gam import fileutils
class FileutilsTest(unittest.TestCase):

View File

@ -6,11 +6,11 @@ import googleapiclient.errors
import google.auth.exceptions
import httplib2
import controlflow
import display
from gapi import errors
import transport
from var import (GM_Globals, GM_CURRENT_API_SCOPES, GM_CURRENT_API_USER,
from gam import controlflow
from gam import display
from gam.gapi import errors
from gam import transport
from gam.var import (GM_Globals, GM_CURRENT_API_SCOPES, GM_CURRENT_API_USER,
GM_EXTRA_ARGS_DICT, GM_OAUTH2SERVICE_ACCOUNT_CLIENT_ID,
MAX_RESULTS_API_EXCEPTIONS, MESSAGE_API_ACCESS_CONFIG,
MESSAGE_API_ACCESS_DENIED, MESSAGE_SERVICE_NOT_APPLICABLE)

View File

@ -6,8 +6,8 @@ from unittest.mock import MagicMock
from unittest.mock import patch
from gam import SetGlobalVariables
import gapi
from gapi import errors
import gam.gapi as gapi
from gam.gapi import errors
def create_http_error(status, reason, message):

View File

@ -3,28 +3,28 @@ import sys
import uuid
# TODO: get rid of these hacks
import __main__
from var import *
import gam
from gam.var import *
import controlflow
import display
import fileutils
import gapi
import utils
from gam import controlflow
from gam import display
from gam import fileutils
from gam import gapi
from gam import utils
def normalizeCalendarId(calname, checkPrimary=False):
if checkPrimary and calname.lower() == 'primary':
return calname
if not GC_Values[GC_DOMAIN]:
GC_Values[GC_DOMAIN] = __main__._getValueFromOAuth('hd')
return __main__.convertUIDtoEmailAddress(calname,
GC_Values[GC_DOMAIN] = gam._getValueFromOAuth('hd')
return gam.convertUIDtoEmailAddress(calname,
email_types=['user', 'resource'])
def buildCalendarGAPIObject(calname):
calendarId = normalizeCalendarId(calname)
return (calendarId, __main__.buildGAPIServiceObject('calendar',
return (calendarId, gam.buildGAPIServiceObject('calendar',
calendarId))
@ -36,9 +36,9 @@ def buildCalendarDataGAPIObject(calname):
# so we need to access them as the admin.
cal = None
if not calname.endswith('.calendar.google.com'):
cal = __main__.buildGAPIServiceObject('calendar', calendarId, False)
cal = gam.buildGAPIServiceObject('calendar', calendarId, False)
if cal is None:
_, cal = buildCalendarGAPIObject(__main__._getValueFromOAuth('email'))
_, cal = buildCalendarGAPIObject(gam._getValueFromOAuth('email'))
return (calendarId, cal)
def printShowACLs(csvFormat):
@ -87,7 +87,7 @@ def _getCalendarACLScope(i, body):
body['scope']['type'] = myarg
i += 1
if myarg in ['user', 'group']:
body['scope']['value'] = __main__.normalizeEmailAddressOrUID(
body['scope']['value'] = gam.normalizeEmailAddressOrUID(
sys.argv[i], noUid=True)
i += 1
elif myarg == 'domain':
@ -99,7 +99,7 @@ def _getCalendarACLScope(i, body):
body['scope']['value'] = GC_Values[GC_DOMAIN]
elif myarg != 'default':
body['scope']['type'] = 'user'
body['scope']['value'] = __main__.normalizeEmailAddressOrUID(
body['scope']['value'] = gam.normalizeEmailAddressOrUID(
myarg, noUid=True)
return i
@ -130,7 +130,7 @@ def addACL(function):
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'sendnotifications':
sendNotifications = __main__.getBoolean(sys.argv[i+1], myarg)
sendNotifications = gam.getBoolean(sys.argv[i+1], myarg)
i += 2
else:
controlflow.invalid_argument_exit(
@ -237,7 +237,7 @@ def getSendUpdates(myarg, i, cal):
sendUpdates = 'all'
i += 1
elif myarg == 'sendnotifications':
sendUpdates = 'all' if __main__.getBoolean(sys.argv[i+1], myarg) else 'none'
sendUpdates = 'all' if gam.getBoolean(sys.argv[i+1], myarg) else 'none'
i += 2
else: # 'sendupdates':
sendUpdatesMap = {}
@ -393,18 +393,18 @@ def getEventAttributes(i, calendarId, cal, body, action):
body['guestsCanInviteOthers'] = False
i += 1
elif myarg == 'guestscaninviteothers':
body['guestsCanInviteTohters'] = __main__.getBoolean(
body['guestsCanInviteTohters'] = gam.getBoolean(
sys.argv[i+1], 'guestscaninviteothers')
i += 2
elif myarg == 'guestscantseeothers':
body['guestsCanSeeOtherGuests'] = False
i += 1
elif myarg == 'guestscanseeothers':
body['guestsCanSeeOtherGuests'] = __main__.getBoolean(
body['guestsCanSeeOtherGuests'] = gam.getBoolean(
sys.argv[i+1], 'guestscanseeothers')
i += 2
elif myarg == 'guestscanmodify':
body['guestsCanModify'] = __main__.getBoolean(
body['guestsCanModify'] = gam.getBoolean(
sys.argv[i+1], 'guestscanmodify')
i += 2
elif myarg == 'id':
@ -458,7 +458,7 @@ def getEventAttributes(i, calendarId, cal, body, action):
i += 1
elif myarg == 'reminder':
minutes = \
__main__.getInteger(sys.argv[i+1], myarg, minVal=0,
gam.getInteger(sys.argv[i+1], myarg, minVal=0,
maxVal=CALENDAR_REMINDER_MAX_MINUTES)
reminder = {'minutes': minutes, 'method': sys.argv[i+2]}
body.setdefault(
@ -483,7 +483,7 @@ def getEventAttributes(i, calendarId, cal, body, action):
body['extendedProperties']['shared'][sys.argv[i+1]] = sys.argv[i+2]
i += 3
elif myarg == 'colorindex':
body['colorId'] = __main__.getInteger(
body['colorId'] = gam.getInteger(
sys.argv[i+1], myarg, CALENDAR_EVENT_MIN_COLOR_INDEX,
CALENDAR_EVENT_MAX_COLOR_INDEX)
i += 2
@ -649,25 +649,25 @@ def getCalendarAttributes(i, body, function):
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'selected':
body['selected'] = __main__.getBoolean(sys.argv[i+1], myarg)
body['selected'] = gam.getBoolean(sys.argv[i+1], myarg)
i += 2
elif myarg == 'hidden':
body['hidden'] = __main__.getBoolean(sys.argv[i+1], myarg)
body['hidden'] = gam.getBoolean(sys.argv[i+1], myarg)
i += 2
elif myarg == 'summary':
body['summaryOverride'] = sys.argv[i+1]
i += 2
elif myarg == 'colorindex':
body['colorId'] = __main__.getInteger(
body['colorId'] = gam.getInteger(
sys.argv[i+1], myarg, minVal=CALENDAR_MIN_COLOR_INDEX,
maxVal=CALENDAR_MAX_COLOR_INDEX)
i += 2
elif myarg == 'backgroundcolor':
body['backgroundColor'] = __main__.getColor(sys.argv[i+1])
body['backgroundColor'] = gam.getColor(sys.argv[i+1])
colorRgbFormat = True
i += 2
elif myarg == 'foregroundcolor':
body['foregroundColor'] = __main__.getColor(sys.argv[i+1])
body['foregroundColor'] = gam.getColor(sys.argv[i+1])
colorRgbFormat = True
i += 2
elif myarg == 'reminder':
@ -677,7 +677,7 @@ def getCalendarAttributes(i, body, function):
if method not in CALENDAR_REMINDER_METHODS:
controlflow.expected_argument_exit("Method", ", ".join(
CALENDAR_REMINDER_METHODS+CLEAR_NONE_ARGUMENT), method)
minutes = __main__.getInteger(
minutes = gam.getInteger(
sys.argv[i+2], myarg, minVal=0,
maxVal=CALENDAR_REMINDER_MAX_MINUTES)
body['defaultReminders'].append(
@ -862,7 +862,7 @@ def transferSecCals(users):
remove_source_user = False
i += 1
elif myarg == 'sendnotifications':
sendNotifications = __main__.getBoolean(sys.argv[i+1], myarg)
sendNotifications = gam.getBoolean(sys.argv[i+1], myarg)
i += 2
else:
controlflow.invalid_argument_exit(

View File

@ -0,0 +1,5 @@
import gam
def buildGAPIObject():
return gam.buildGAPIObject('directory')

View File

@ -1,17 +1,17 @@
import datetime
from var import *
import __main__
import controlflow
import display
import fileutils
import gapi
import gapi.directory
import utils
from gam.var import *
import gam
from gam import controlflow
from gam import display
from gam import fileutils
from gam import gapi
from gam.gapi import directory as gapi_directory
from gam import utils
def doUpdateCros():
cd = gapi.directory.buildGAPIObject()
cd = gapi_directory.buildGAPIObject()
i, devices = getCrOSDeviceEntity(3, cd)
update_body = {}
action_body = {}
@ -32,7 +32,7 @@ def doUpdateCros():
update_body['annotatedAssetId'] = sys.argv[i+1]
i += 2
elif myarg in ['ou', 'org']:
orgUnitPath = __main__.getOrgUnitItem(sys.argv[i+1])
orgUnitPath = gam.getOrgUnitItem(sys.argv[i+1])
i += 2
elif myarg == 'action':
action = sys.argv[i+1].lower().replace('_', '').replace('-', '')
@ -84,7 +84,7 @@ def doUpdateCros():
sys.exit(3)
for deviceId in devices:
i += 1
cur_count = __main__.currentCount(i, count)
cur_count = gam.currentCount(i, count)
print(f' performing action {action} for {deviceId}{cur_count}')
gapi.call(cd.chromeosdevices(), function='action',
customerId=GC_Values[GC_CUSTOMER_ID],
@ -93,7 +93,7 @@ def doUpdateCros():
if update_body:
for deviceId in devices:
i += 1
current_count = __main__.currentCount(i, count)
current_count = gam.currentCount(i, count)
print(f' updating {deviceId}{current_count}')
gapi.call(cd.chromeosdevices(), 'update',
customerId=GC_Values[GC_CUSTOMER_ID],
@ -110,7 +110,7 @@ def doUpdateCros():
def doGetCrosInfo():
cd = gapi.directory.buildGAPIObject()
cd = gapi_directory.buildGAPIObject()
i, devices = getCrOSDeviceEntity(3, cd)
downloadfile = None
targetFolder = GC_Values[GC_DRIVE_DIR]
@ -125,7 +125,7 @@ def doGetCrosInfo():
noLists = True
i += 1
elif myarg == 'listlimit':
listLimit = __main__.getInteger(sys.argv[i+1], myarg, minVal=-1)
listLimit = gam.getInteger(sys.argv[i+1], myarg, minVal=-1)
i += 2
elif myarg in CROS_START_ARGUMENTS:
startDate = _getFilterDate(sys.argv[i+1])
@ -318,7 +318,7 @@ def doGetCrosInfo():
def doPrintCrosActivity():
cd = gapi.directory.buildGAPIObject()
cd = gapi_directory.buildGAPIObject()
todrive = False
titles = ['deviceId', 'annotatedAssetId',
'annotatedLocation', 'serialNumber', 'orgUnitPath']
@ -335,10 +335,10 @@ def doPrintCrosActivity():
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg in ['query', 'queries']:
queries = __main__.getQueries(myarg, sys.argv[i+1])
queries = gam.getQueries(myarg, sys.argv[i+1])
i += 2
elif myarg == 'limittoou':
orgUnitPath = __main__.getOrgUnitItem(sys.argv[i+1])
orgUnitPath = gam.getOrgUnitItem(sys.argv[i+1])
i += 2
elif myarg == 'todrive':
todrive = True
@ -366,7 +366,7 @@ def doPrintCrosActivity():
endDate = _getFilterDate(sys.argv[i+1])
i += 2
elif myarg == 'listlimit':
listLimit = __main__.getInteger(sys.argv[i+1], myarg, minVal=0)
listLimit = gam.getInteger(sys.argv[i+1], myarg, minVal=0)
i += 2
elif myarg == 'delimiter':
delimiter = sys.argv[i+1]
@ -393,7 +393,7 @@ def doPrintCrosActivity():
display.add_titles_to_csv_file(titles_to_add, titles)
fields = f'nextPageToken,chromeosdevices({",".join(fieldsList)})'
for query in queries:
__main__.printGettingAllItems('CrOS Devices', query)
gam.printGettingAllItems('CrOS Devices', query)
page_message = gapi.got_total_items_msg('CrOS Devices', '...\n')
all_cros = gapi.get_all_pages(cd.chromeosdevices(), 'list',
'chromeosdevices',
@ -479,7 +479,7 @@ def doPrintCrosDevices():
elif myarg in CROS_SYSTEM_RAM_FREE_REPORTS_ARGUMENTS:
selectedLists['systemRamFreeReports'] = True
cd = gapi.directory.buildGAPIObject()
cd = gapi_directory.buildGAPIObject()
todrive = False
fieldsList = []
fieldsTitles = {}
@ -497,10 +497,10 @@ def doPrintCrosDevices():
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg in ['query', 'queries']:
queries = __main__.getQueries(myarg, sys.argv[i+1])
queries = gam.getQueries(myarg, sys.argv[i+1])
i += 2
elif myarg == 'limittoou':
orgUnitPath = __main__.getOrgUnitItem(sys.argv[i+1])
orgUnitPath = gam.getOrgUnitItem(sys.argv[i+1])
i += 2
elif myarg == 'todrive':
todrive = True
@ -510,7 +510,7 @@ def doPrintCrosDevices():
selectedLists = {}
i += 1
elif myarg == 'listlimit':
listLimit = __main__.getInteger(sys.argv[i+1], myarg, minVal=0)
listLimit = gam.getInteger(sys.argv[i+1], myarg, minVal=0)
i += 2
elif myarg in CROS_START_ARGUMENTS:
startDate = _getFilterDate(sys.argv[i+1])
@ -589,7 +589,7 @@ def doPrintCrosDevices():
else:
fields = None
for query in queries:
__main__.printGettingAllItems('CrOS Devices', query)
gam.printGettingAllItems('CrOS Devices', query)
page_message = gapi.got_total_items_msg('CrOS Devices', '...\n')
all_cros = gapi.get_all_pages(cd.chromeosdevices(), 'list',
'chromeosdevices',
@ -742,9 +742,9 @@ def doPrintCrosDevices():
def getCrOSDeviceEntity(i, cd):
myarg = sys.argv[i].lower()
if myarg == 'cros_sn':
return i+2, __main__.getUsersToModify('cros_sn', sys.argv[i+1])
return i+2, gam.getUsersToModify('cros_sn', sys.argv[i+1])
if myarg == 'query':
return i+2, __main__.getUsersToModify('crosquery', sys.argv[i+1])
return i+2, gam.getUsersToModify('crosquery', sys.argv[i+1])
if myarg[:6] == 'query:':
query = sys.argv[i][6:]
if query[:12].lower() == 'orgunitpath:':

View File

@ -1,14 +1,14 @@
import datetime
from var import *
import controlflow
import gapi
import gapi.directory
import gapi.reports
from gam.var import *
from gam import controlflow
from gam import gapi
from gam.gapi import directory as gapi_directory
from gam.gapi import reports as gapi_reports
def doGetCustomerInfo():
cd = gapi.directory.buildGAPIObject()
cd = gapi_directory.buildGAPIObject()
customer_info = gapi.call(cd.customers(), 'get',
customerKey=GC_Values[GC_CUSTOMER_ID])
print(f'Customer ID: {customer_info["id"]}')
@ -59,7 +59,7 @@ def doGetCustomerInfo():
customerId = GC_Values[GC_CUSTOMER_ID]
if customerId == MY_CUSTOMER:
customerId = None
rep = gapi.reports.buildGAPIObject()
rep = gapi_reports.buildGAPIObject()
usage = None
throw_reasons = [gapi.errors.ErrorReason.INVALID]
while True:
@ -71,7 +71,7 @@ def doGetCustomerInfo():
parameters=parameters)
break
except gapi.errors.GapiInvalidError as e:
tryDate = gapi.reports._adjust_date(str(e))
tryDate = gapi_reports._adjust_date(str(e))
if not usage:
print('No user count data available.')
return
@ -84,7 +84,7 @@ def doGetCustomerInfo():
def doUpdateCustomer():
cd = gapi.directory.buildGAPIObject()
cd = gapi_directory.buildGAPIObject()
body = {}
i = 3
while i < len(sys.argv):

View File

@ -1,17 +1,18 @@
import sys
import uuid
import __main__
from var import *
import controlflow
import display
import gapi.directory
import utils
import gam
from gam.var import *
from gam import controlflow
from gam import display
from gam import gapi
from gam.gapi import directory as gapi_directory
from gam import utils
def printBuildings():
to_drive = False
cd = gapi.directory.buildGAPIObject()
cd = gapi_directory.buildGAPIObject()
titles = []
csvRows = []
fieldsList = ['buildingId']
@ -65,7 +66,7 @@ def printBuildings():
def printResourceCalendars():
cd = gapi.directory.buildGAPIObject()
cd = gapi_directory.buildGAPIObject()
todrive = False
fieldsList = []
fieldsTitles = {}
@ -108,7 +109,7 @@ def printResourceCalendars():
if 'buildingId' in fieldsList:
display.add_field_to_csv_file('buildingName', {'buildingName': [
'buildingName', ]}, fieldsList, fieldsTitles, titles)
__main__.printGettingAllItems('Resource Calendars', None)
gam.printGettingAllItems('Resource Calendars', None)
page_message = gapi.got_total_items_first_last_msg('Resource Calendars')
resources = gapi.get_all_pages(cd.resources().calendars(), 'list',
'items', page_message=page_message,
@ -162,7 +163,7 @@ RESCAL_ARGUMENT_TO_PROPERTY_MAP = {
def printFeatures():
to_drive = False
cd = gapi.directory.buildGAPIObject()
cd = gapi_directory.buildGAPIObject()
titles = []
csvRows = []
fieldsList = ['name']
@ -240,7 +241,7 @@ def _getBuildingAttributes(args, body={}):
def createBuilding():
cd = gapi.directory.buildGAPIObject()
cd = gapi_directory.buildGAPIObject()
body = {'floorNames': ['1'],
'buildingId': str(uuid.uuid4()),
'buildingName': sys.argv[3]}
@ -318,7 +319,7 @@ def getBuildingNameById(cd, buildingId):
def updateBuilding():
cd = gapi.directory.buildGAPIObject()
cd = gapi_directory.buildGAPIObject()
buildingId = getBuildingByNameOrId(cd, sys.argv[3])
body = _getBuildingAttributes(sys.argv[4:])
print(f'Updating building {buildingId}...')
@ -328,7 +329,7 @@ def updateBuilding():
def getBuildingInfo():
cd = gapi.directory.buildGAPIObject()
cd = gapi_directory.buildGAPIObject()
buildingId = getBuildingByNameOrId(cd, sys.argv[3])
building = gapi.call(cd.resources().buildings(), 'get',
customer=GC_Values[GC_CUSTOMER_ID],
@ -343,7 +344,7 @@ def getBuildingInfo():
def deleteBuilding():
cd = gapi.directory.buildGAPIObject()
cd = gapi_directory.buildGAPIObject()
buildingId = getBuildingByNameOrId(cd, sys.argv[3])
print(f'Deleting building {buildingId}...')
gapi.call(cd.resources().buildings(), 'delete',
@ -364,7 +365,7 @@ def _getFeatureAttributes(args, body={}):
def createFeature():
cd = gapi.directory.buildGAPIObject()
cd = gapi_directory.buildGAPIObject()
body = _getFeatureAttributes(sys.argv[3:])
print(f'Creating feature {body["name"]}...')
gapi.call(cd.resources().features(), 'insert',
@ -375,7 +376,7 @@ def updateFeature():
# update does not work for name and name is only field to be updated
# if additional writable fields are added to feature in the future
# we'll add support for update as well as rename
cd = gapi.directory.buildGAPIObject()
cd = gapi_directory.buildGAPIObject()
oldName = sys.argv[3]
body = {'newName': sys.argv[5:]}
print(f'Updating feature {oldName}...')
@ -385,7 +386,7 @@ def updateFeature():
def deleteFeature():
cd = gapi.directory.buildGAPIObject()
cd = gapi_directory.buildGAPIObject()
featureKey = sys.argv[3]
print(f'Deleting feature {featureKey}...')
gapi.call(cd.resources().features(), 'delete',
@ -410,7 +411,7 @@ def _getResourceCalendarAttributes(cd, args, body={}):
cd, args[i+1], minLen=0)
i += 2
elif myarg in ['capacity']:
body['capacity'] = __main__.getInteger(args[i+1], myarg, minVal=0)
body['capacity'] = gam.getInteger(args[i+1], myarg, minVal=0)
i += 2
elif myarg in ['feature', 'features']:
features = args[i+1].split(',')
@ -440,7 +441,7 @@ def _getResourceCalendarAttributes(cd, args, body={}):
def createResourceCalendar():
cd = gapi.directory.buildGAPIObject()
cd = gapi_directory.buildGAPIObject()
body = {'resourceId': sys.argv[3],
'resourceName': sys.argv[4]}
body = _getResourceCalendarAttributes(cd, sys.argv[5:], body)
@ -450,7 +451,7 @@ def createResourceCalendar():
def updateResourceCalendar():
cd = gapi.directory.buildGAPIObject()
cd = gapi_directory.buildGAPIObject()
resId = sys.argv[3]
body = _getResourceCalendarAttributes(cd, sys.argv[4:])
# Use patch since it seems to work better.
@ -462,7 +463,7 @@ def updateResourceCalendar():
def getResourceCalendarInfo():
cd = gapi.directory.buildGAPIObject()
cd = gapi_directory.buildGAPIObject()
resId = sys.argv[3]
resource = gapi.call(cd.resources().calendars(), 'get',
customer=GC_Values[GC_CUSTOMER_ID],
@ -481,7 +482,7 @@ def getResourceCalendarInfo():
def deleteResourceCalendar():
resId = sys.argv[3]
cd = gapi.directory.buildGAPIObject()
cd = gapi_directory.buildGAPIObject()
print(f'Deleting resource calendar {resId}')
gapi.call(cd.resources().calendars(), 'delete',
customer=GC_Values[GC_CUSTOMER_ID], calendarResourceId=resId)

View File

@ -3,9 +3,9 @@
from enum import Enum
import json
import controlflow
import display # TODO: Change to relative import when gam is setup as a package
from var import UTF8
from gam import controlflow
from gam import display
from gam.var import UTF8
class GapiAbortedError(Exception):

View File

@ -6,7 +6,7 @@ import unittest
from unittest.mock import patch
import googleapiclient.errors
from gapi import errors
from gam.gapi import errors
def create_simple_http_error(status, reason, message):

View File

@ -5,16 +5,16 @@ import sys
from dateutil.relativedelta import relativedelta
import __main__
from var import *
import controlflow
import display
import gapi
import utils
import gam
from gam.var import *
from gam import controlflow
from gam import display
from gam import gapi
from gam import utils
def buildGAPIObject():
return __main__.buildGAPIObject('reports')
return gam.buildGAPIObject('reports')
REPORT_CHOICE_MAP = {
@ -55,7 +55,7 @@ def showUsageParameters():
kwargs = {}
elif report == 'user':
endpoint = rep.userUsageReport()
kwargs = {'userKey': __main__._getValueFromOAuth('email')}
kwargs = {'userKey': gam._getValueFromOAuth('email')}
else:
controlflow.expected_argument_exit(
'usageparameters', ['user', 'customer'], report)
@ -170,10 +170,10 @@ def showUsage():
skip_day_numbers = [dow.index(d) for d in skipdaynames if d in dow]
i += 2
elif report == 'user' and myarg in ['orgunit', 'org', 'ou']:
_, orgUnitId = __main__.getOrgUnitId(sys.argv[i+1])
_, orgUnitId = gam.getOrgUnitId(sys.argv[i+1])
i += 2
elif report == 'user' and myarg in usergroup_types:
users = __main__.getUsersToModify(myarg, sys.argv[i+1])
users = gam.getUsersToModify(myarg, sys.argv[i+1])
kwargs = [{'userKey': user} for user in users]
i += 2
else:
@ -286,7 +286,7 @@ def showReport():
tryDate = utils.get_yyyymmdd(sys.argv[i+1])
i += 2
elif myarg in ['orgunit', 'org', 'ou']:
_, orgUnitId = __main__.getOrgUnitId(sys.argv[i+1])
_, orgUnitId = gam.getOrgUnitId(sys.argv[i+1])
i += 2
elif myarg == 'fulldatarequired':
fullDataRequired = []
@ -304,7 +304,7 @@ def showReport():
eventName = sys.argv[i+1]
i += 2
elif myarg == 'user':
userKey = __main__.normalizeEmailAddressOrUID(sys.argv[i+1])
userKey = gam.normalizeEmailAddressOrUID(sys.argv[i+1])
i += 2
elif myarg in ['filter', 'filters']:
filters = sys.argv[i+1]

View File

@ -5,16 +5,15 @@ import sys
import googleapiclient
import __main__
from var import *
import controlflow
import fileutils
import gapi
import utils
import gam
from gam.var import *
from gam import fileutils
from gam import gapi
from gam import utils
def build_gapi():
return __main__.buildGAPIObject('storage')
return gam.buildGAPIObject('storage')
def get_cloud_storage_object(s, bucket, object_, local_file=None,

View File

@ -4,24 +4,24 @@ import sys
import googleapiclient.http
import __main__
from var import *
import controlflow
import display
import fileutils
import gapi
import gapi.storage
import utils
import gam
from gam.var import *
from gam import controlflow
from gam import display
from gam import fileutils
from gam import gapi
from gam.gapi import storage as gapi_storage
from gam import utils
def buildGAPIObject():
return __main__.buildGAPIObject('vault')
return gam.buildGAPIObject('vault')
def validateCollaborators(collaboratorList, cd):
collaborators = []
for collaborator in collaboratorList.split(','):
collaborator_id = __main__.convertEmailAddressToUID(collaborator, cd)
collaborator_id = gam.convertEmailAddressToUID(collaborator, cd)
if not collaborator_id:
controlflow.system_error_exit(4, f'failed to get a UID for '
f'{collaborator}. Please make '
@ -47,7 +47,7 @@ def createMatter():
i += 2
elif myarg in ['collaborator', 'collaborators']:
if not cd:
cd = __main__.buildGAPIObject('directory')
cd = gam.buildGAPIObject('directory')
collaborators.extend(validateCollaborators(sys.argv[i+1], cd))
i += 2
else:
@ -124,7 +124,7 @@ def createExport():
i += 2
elif searchMethod == 'ORG_UNIT':
body['query']['orgUnitInfo'] = {
'orgUnitId': __main__.getOrgUnitId(sys.argv[i+1])[1]}
'orgUnitId': gam.getOrgUnitId(sys.argv[i+1])[1]}
i += 2
elif searchMethod == 'SHARED_DRIVE':
body['query']['sharedDriveInfo'] = {
@ -158,7 +158,7 @@ def createExport():
i += 2
elif myarg in ['excludedrafts']:
body['query']['mailOptions'] = {
'excludeDrafts': __main__.getBoolean(sys.argv[i+1], myarg)}
'excludeDrafts': gam.getBoolean(sys.argv[i+1], myarg)}
i += 2
elif myarg in ['driveversiondate']:
body['query'].setdefault('driveOptions', {})['versionDate'] = \
@ -166,11 +166,11 @@ def createExport():
i += 2
elif myarg in ['includeshareddrives', 'includeteamdrives']:
body['query'].setdefault('driveOptions', {})[
'includeSharedDrives'] = __main__.getBoolean(sys.argv[i+1], myarg)
'includeSharedDrives'] = gam.getBoolean(sys.argv[i+1], myarg)
i += 2
elif myarg in ['includerooms']:
body['query']['hangoutsChatOptions'] = {
'includeRooms': __main__.getBoolean(sys.argv[i+1], myarg)}
'includeRooms': gam.getBoolean(sys.argv[i+1], myarg)}
i += 2
elif myarg in ['format']:
export_format = sys.argv[i+1].upper()
@ -179,7 +179,7 @@ def createExport():
"export format", ", ".join(allowed_formats), export_format)
i += 2
elif myarg in ['showconfidentialmodecontent']:
showConfidentialModeContent = __main__.getBoolean(sys.argv[i+1], myarg)
showConfidentialModeContent = gam.getBoolean(sys.argv[i+1], myarg)
i += 2
elif myarg in ['region']:
allowed_regions = gapi.get_enum_values_minus_unspecified(
@ -192,7 +192,7 @@ def createExport():
i += 2
elif myarg in ['includeaccessinfo']:
body['exportOptions'].setdefault('driveOptions', {})[
'includeAccessInfo'] = __main__.getBoolean(sys.argv[i+1], myarg)
'includeAccessInfo'] = gam.getBoolean(sys.argv[i+1], myarg)
i += 2
else:
controlflow.invalid_argument_exit(sys.argv[i], "gam create export")
@ -277,7 +277,7 @@ def createHold():
i += 2
elif myarg in ['orgunit', 'ou']:
body['orgUnit'] = {
'orgUnitId': __main__.getOrgUnitId(sys.argv[i+1])[1]}
'orgUnitId': gam.getOrgUnitId(sys.argv[i+1])[1]}
i += 2
elif myarg in ['start', 'starttime']:
start_time = utils.get_date_zero_time_or_full_time(sys.argv[i+1])
@ -319,11 +319,11 @@ def createHold():
body['query'][query_type]['endTime'] = end_time
if accounts:
body['accounts'] = []
cd = __main__.buildGAPIObject('directory')
cd = gam.buildGAPIObject('directory')
account_type = 'group' if body['corpus'] == 'GROUPS' else 'user'
for account in accounts:
body['accounts'].append(
{'accountId': __main__.convertEmailAddressToUID(account,
{'accountId': gam.convertEmailAddressToUID(account,
cd,
account_type)}
)
@ -370,16 +370,16 @@ def getHoldInfo():
3, 'you must specify a matter for the hold.')
results = gapi.call(v.matters().holds(), 'get',
matterId=matterId, holdId=holdId)
cd = __main__.buildGAPIObject('directory')
cd = gam.buildGAPIObject('directory')
if 'accounts' in results:
account_type = 'group' if results['corpus'] == 'GROUPS' else 'user'
for i in range(0, len(results['accounts'])):
uid = f'uid:{results["accounts"][i]["accountId"]}'
acct_email = __main__.convertUIDtoEmailAddress(
acct_email = gam.convertUIDtoEmailAddress(
uid, cd, [account_type])
results['accounts'][i]['email'] = acct_email
if 'orgUnit' in results:
results['orgUnit']['orgUnitPath'] = __main__.doGetOrgInfo(
results['orgUnit']['orgUnitPath'] = gam.doGetOrgInfo(
results['orgUnit']['orgUnitId'], return_attrib='orgUnitPath')
display.print_json(results)
@ -456,7 +456,7 @@ def updateHold():
query = sys.argv[i+1]
i += 2
elif myarg in ['orgunit', 'ou']:
body['orgUnit'] = {'orgUnitId': __main__.getOrgUnitId(sys.argv[i+1])[1]}
body['orgUnit'] = {'orgUnitId': gam.getOrgUnitId(sys.argv[i+1])[1]}
i += 2
elif myarg in ['start', 'starttime']:
start_time = utils.get_date_zero_time_or_full_time(sys.argv[i+1])
@ -505,15 +505,15 @@ def updateHold():
gapi.call(v.matters().holds(), 'update',
matterId=matterId, holdId=holdId, body=body)
if add_accounts or del_accounts:
cd = __main__.buildGAPIObject('directory')
cd = gam.buildGAPIObject('directory')
for account in add_accounts:
print(f'adding {account} to hold.')
add_body = {'accountId': __main__.convertEmailAddressToUID(account, cd)}
add_body = {'accountId': gam.convertEmailAddressToUID(account, cd)}
gapi.call(v.matters().holds().accounts(), 'create',
matterId=matterId, holdId=holdId, body=add_body)
for account in del_accounts:
print(f'removing {account} from hold.')
accountId = __main__.convertEmailAddressToUID(account, cd)
accountId = gam.convertEmailAddressToUID(account, cd)
gapi.call(v.matters().holds().accounts(), 'delete',
matterId=matterId, holdId=holdId, accountId=accountId)
@ -543,12 +543,12 @@ def updateMatter(action=None):
i += 2
elif myarg in ['addcollaborator', 'addcollaborators']:
if not cd:
cd = __main__.buildGAPIObject('directory')
cd = gam.buildGAPIObject('directory')
add_collaborators.extend(validateCollaborators(sys.argv[i+1], cd))
i += 2
elif myarg in ['removecollaborator', 'removecollaborators']:
if not cd:
cd = __main__.buildGAPIObject('directory')
cd = gam.buildGAPIObject('directory')
remove_collaborators.extend(
validateCollaborators(sys.argv[i+1], cd))
i += 2
@ -585,10 +585,10 @@ def getMatterInfo():
matterId = getMatterItem(v, sys.argv[3])
result = gapi.call(v.matters(), 'get', matterId=matterId, view='FULL')
if 'matterPermissions' in result:
cd = __main__.buildGAPIObject('directory')
cd = gam.buildGAPIObject('directory')
for i in range(0, len(result['matterPermissions'])):
uid = f'uid:{result["matterPermissions"][i]["accountId"]}'
user_email = __main__.convertUIDtoEmailAddress(uid, cd)
user_email = gam.convertUIDtoEmailAddress(uid, cd)
result['matterPermissions'][i]['email'] = user_email
display.print_json(result)
@ -597,7 +597,7 @@ def downloadExport():
verifyFiles = True
extractFiles = True
v = buildGAPIObject()
s = gapi.storage.build_gapi()
s = gapi_storage.build_gapi()
matterId = getMatterItem(v, sys.argv[3])
exportId = convertExportNameToID(v, sys.argv[4], matterId)
targetFolder = GC_Values[GC_DRIVE_DIR]
@ -643,7 +643,7 @@ def downloadExport():
utils.md5_matches_file(filename, expected_hash, True)
print('VERIFIED')
if extractFiles and re.search(r'\.zip$', filename):
__main__.extract_nested_zip(filename, targetFolder)
gam.extract_nested_zip(filename, targetFolder)
def printMatters():
@ -674,7 +674,7 @@ def printMatters():
i += 2
else:
controlflow.invalid_argument_exit(myarg, "gam print matters")
__main__.printGettingAllItems('Vault Matters', None)
gam.printGettingAllItems('Vault Matters', None)
page_message = gapi.got_total_items_msg('Vault Matters', '...\n')
matters = gapi.get_all_pages(
v.matters(), 'list', 'matters', page_message=page_message, view=view,

View File

@ -3,11 +3,11 @@
import google_auth_httplib2
import httplib2
from var import GAM_INFO
from var import GC_CA_FILE
from var import GC_TLS_MAX_VERSION
from var import GC_TLS_MIN_VERSION
from var import GC_Values
from gam.var import GAM_INFO
from gam.var import GC_CA_FILE
from gam.var import GC_TLS_MAX_VERSION
from gam.var import GC_TLS_MIN_VERSION
from gam.var import GC_Values
def create_http(cache=None,

View File

@ -8,7 +8,7 @@ from gam import SetGlobalVariables
import google_auth_httplib2
import httplib2
import transport
from gam import transport
class CreateHttpTest(unittest.TestCase):

View File

@ -8,10 +8,10 @@ from html.parser import HTMLParser
import json
import dateutil.parser
import controlflow
import fileutils
import transport
from var import *
from gam import controlflow
from gam import fileutils
from gam import transport
from gam.var import *
class _DeHTMLParser(HTMLParser):

View File

@ -1,5 +0,0 @@
import __main__
def buildGAPIObject():
return __main__.buildGAPIObject('directory')

View File

@ -1,11 +1,11 @@
cd src
if [[ "$TRAVIS_JOB_NAME" == *"Testing" ]]; then
export gam="$python gam.py"
export gam="$python -m gam"
export gampath=$(readlink -e .)
else
$python -OO -m PyInstaller --clean --noupx --strip -F --distpath=gam gam.spec
export gam="gam/gam"
$python -OO -m PyInstaller --clean --noupx --strip -F gam.spec
export gampath=$(readlink -e gam)
export gam="${gampath}/gam"
export GAMVERSION=`$gam version simple`
cp LICENSE $gampath
cp whatsnew.txt $gampath
@ -13,21 +13,21 @@ else
this_glibc_ver=$(ldd --version | awk '/ldd/{print $NF}')
GAM_ARCHIVE=gam-$GAMVERSION-$GAMOS-$PLATFORM-glibc$this_glibc_ver.tar.xz
rm $gampath/lastupdatecheck.txt
tar cfJ $GAM_ARCHIVE gam/
tar cfJ --transform s/dist/gam/ $GAM_ARCHIVE $gampath
echo "PyInstaller GAM info:"
du -h gam/gam
du -h $gam
time $gam version extended
if ([ "${TRAVIS_DIST}" == "trusty" ] || [ "${TRAVIS_DIST}" == "xenial" ]) && [ "${PLATFORM}" == "x86_64" ]; then
if [ "${TRAVIS_DIST}" == "xenial" ] && [ "${PLATFORM}" == "x86_64" ]; then
GAM_LEGACY_ARCHIVE=gam-${GAMVERSION}-${GAMOS}-${PLATFORM}-legacy.tar.xz
$python -OO -m staticx -l /lib/x86_64-linux-gnu/libresolv.so.2 -l /lib/x86_64-linux-gnu/libnss_dns.so.2 gam/gam gam/gam-staticx
strip gam/gam-staticx
rm gam/gam
mv gam/gam-staticx gam/gam
chmod 755 gam/gam
tar cvfJ $GAM_LEGACY_ARCHIVE gam/
$python -OO -m staticx -l /lib/x86_64-linux-gnu/libresolv.so.2 -l /lib/x86_64-linux-gnu/libnss_dns.so.2 $gam $gam-staticx
strip $gam-staticx
rm $gampath/gam
mv $gam-staticx $gam
chmod 755 $gam
tar cvfJ --transform s/dist/gam/ $GAM_LEGACY_ARCHIVE $gampath
echo "Legacy StaticX GAM info:"
du -h gam/gam
du -h $gam
time $gam version extended
fi
echo "GAM packages:"

View File

@ -1,15 +1,15 @@
cd src
echo "MacOS Version Info According to Python:"
python -c "import platform; print(platform.mac_ver())"
$python -OO -m PyInstaller --clean --noupx --strip -F --distpath=gam gam.spec
export gam="gam/gam"
export gampath=gam
$python -OO -m PyInstaller --clean --noupx --strip -F gam.spec
export gampath=dist
export gam="$gampath/gam"
$gam version extended
export GAMVERSION=`gam/gam version simple`
cp LICENSE gam
cp whatsnew.txt gam
cp GamCommands.txt gam
export GAMVERSION=`$gam version simple`
cp LICENSE $gampath
cp whatsnew.txt $gampath
cp GamCommands.txt $gampath
MACOSVERSION=$(defaults read loginwindow SystemVersionStampAsString)
GAM_ARCHIVE=gam-$GAMVERSION-$GAMOS-$PLATFORM-MacOS$MACOSVERSION.tar.xz
rm gam/lastupdatecheck.txt
tar cfJ $GAM_ARCHIVE gam/
rm $gampath/lastupdatecheck.txt
tar cfJ $GAM_ARCHIVE --transform s/$gampath/gam $gampath

View File

@ -1,20 +1,21 @@
cd src
echo "compiling GAM with pyinstaller..."
pyinstaller --clean --noupx -F --distpath=gam gam.spec
export gam="gam/gam"
pyinstaller --clean --noupx -F gam.spec
export gampath=$(readlink -e gam)
export gam="${gampath}/gam"
echo "running compiled GAM..."
$gam version
export GAMVERSION=`$gam version simple`
rm gam/lastupdatecheck.txt
cp LICENSE gam
cp GamCommands.txt gam
cp whatsnew.txt gam
cp gam-setup.bat gam
rm $gampath/lastupdatecheck.txt
cp LICENSE $gampath
cp GamCommands.txt $gampath
cp whatsnew.txt $gampath
cp gam-setup.bat $gampath
GAM_ARCHIVE=gam-$GAMVERSION-$GAMOS-$PLATFORM.zip
/c/Program\ Files/7-Zip/7z.exe a -tzip $GAM_ARCHIVE gam -xr!.svn
/c/Program\ Files/7-Zip/7z.exe a -tzip $GAM_ARCHIVE $gampath -xr!.svn
/c/Program\ Files/7-Zip/7z.exe rn $GAM_ARCHIVE dist\ gam\
mkdir gam-64
cp -rf gam/* gam-64/;
cp -rf $gampath/* gam-64/
/c/Program\ Files\ \(x86\)/WiX\ Toolset\ v3.11/bin/candle.exe -arch $WIX_BITS gam.wxs
/c/Program\ Files\ \(x86\)/WiX\ Toolset\ v3.11/bin/light.exe -ext /c/Program\ Files\ \(x86\)/WiX\ Toolset\ v3.11/bin/WixUIExtension.dll gam.wixobj -o gam-$GAMVERSION-$GAMOS-$PLATFORM.msi || true;
rm *.wixpdb