diff --git a/.gitignore b/.gitignore index c2833fdd..21505551 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,8 @@ project_root_marker /.vim_l +/.idea + *.pyc *.csproj.user diff --git a/Projeny.yaml b/Projeny.yaml index 12dfdaff..ad8e3c85 100644 --- a/Projeny.yaml +++ b/Projeny.yaml @@ -31,7 +31,7 @@ PathVars: # Note that these are defaults, and can be overridden in any other Projeny.yaml file MsBuildExePath: 'C:/Windows/Microsoft.NET/Framework/v4.0.30319/msbuild.exe' - UnityExePath: 'C:/Program Files/Unity/Editor/Unity.exe' + UnityExePath: '/Applications/Unity/Unity.app' Console: UseColors: False diff --git a/Source/bin/PrjEditorApi b/Source/bin/PrjEditorApi new file mode 100755 index 00000000..676ac8f6 --- /dev/null +++ b/Source/bin/PrjEditorApi @@ -0,0 +1,5 @@ +cd ./.. + +command -v python3 >/dev/null 2>&1 || { echo -e >&2 "I require python 3.x but it's not installed.\nPlease install Python 3.x before running Projeny.\nAborting."; exit 1; } + +python3 -m prj.main.EditorApi "$@" diff --git a/Source/bin/prj b/Source/bin/prj new file mode 100755 index 00000000..09f6de23 --- /dev/null +++ b/Source/bin/prj @@ -0,0 +1,9 @@ +#!/bin/bash + +dir=$(pwd) + +cd $(dirname "$0")/.. + +command -v /usr/local/bin/python3 >/dev/null 2>&1 || { echo -e >&2 "I require python 3.x but it's not installed.\nPlease install Python 3.x before running Projeny.\nAborting."; exit 1; } + +/usr/local/bin/python3 -m prj.main.Prj $dir "$@" diff --git a/Source/mtm/config/Config.py b/Source/mtm/config/Config.py index d4524ba0..13217f86 100644 --- a/Source/mtm/config/Config.py +++ b/Source/mtm/config/Config.py @@ -1,13 +1,6 @@ - -import yaml - import mtm.util.Util as Util from mtm.util.Assert import * -import mtm.ioc.Container as Container -from mtm.ioc.Inject import Inject -from mtm.ioc.Inject import InjectOptional -import mtm.ioc.IocAssertions as Assertions from collections import OrderedDict diff --git a/Source/mtm/log/ColorConsole.py b/Source/mtm/log/ColorConsole.py deleted file mode 100644 index 87cddc19..00000000 --- a/Source/mtm/log/ColorConsole.py +++ /dev/null @@ -1,84 +0,0 @@ -# Code obtained from here -# https://www.burgaud.com/bring-colors-to-the-windows-console-with-python/ -# Which says MIT license - -""" - -Colors text in console mode application (win32). -Uses ctypes and Win32 methods SetConsoleTextAttribute and -GetConsoleScreenBufferInfo. - -$Id: color_console.py 534 2009-05-10 04:00:59Z andre $ - -""" - -from ctypes import windll, Structure, c_short, c_ushort, byref - -SHORT = c_short -WORD = c_ushort - -class COORD(Structure): - """struct in wincon.h.""" - _fields_ = [ - ("X", SHORT), - ("Y", SHORT)] - -class SMALL_RECT(Structure): - """struct in wincon.h.""" - _fields_ = [ - ("Left", SHORT), - ("Top", SHORT), - ("Right", SHORT), - ("Bottom", SHORT)] - -class CONSOLE_SCREEN_BUFFER_INFO(Structure): - """struct in wincon.h.""" - _fields_ = [ - ("dwSize", COORD), - ("dwCursorPosition", COORD), - ("wAttributes", WORD), - ("srWindow", SMALL_RECT), - ("dwMaximumWindowSize", COORD)] - -# winbase.h -STD_INPUT_HANDLE = -10 -STD_OUTPUT_HANDLE = -11 -STD_ERROR_HANDLE = -12 - -# wincon.h -FOREGROUND_BLACK = 0x0000 -FOREGROUND_BLUE = 0x0001 -FOREGROUND_GREEN = 0x0002 -FOREGROUND_CYAN = 0x0003 -FOREGROUND_RED = 0x0004 -FOREGROUND_MAGENTA = 0x0005 -FOREGROUND_YELLOW = 0x0006 -FOREGROUND_GREY = 0x0007 -FOREGROUND_INTENSITY = 0x0008 # foreground color is intensified. - -BACKGROUND_BLACK = 0x0000 -BACKGROUND_BLUE = 0x0010 -BACKGROUND_GREEN = 0x0020 -BACKGROUND_CYAN = 0x0030 -BACKGROUND_RED = 0x0040 -BACKGROUND_MAGENTA = 0x0050 -BACKGROUND_YELLOW = 0x0060 -BACKGROUND_GREY = 0x0070 -BACKGROUND_INTENSITY = 0x0080 # background color is intensified. - -stdout_handle = windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE) -SetConsoleTextAttribute = windll.kernel32.SetConsoleTextAttribute -GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo - -def get_text_attr(): - """Returns the character attributes (colors) of the console screen - buffer.""" - csbi = CONSOLE_SCREEN_BUFFER_INFO() - GetConsoleScreenBufferInfo(stdout_handle, byref(csbi)) - return csbi.wAttributes - -def set_text_attr(color): - """Sets the character attributes (colors) of the console screen - buffer. Color is a combination of foreground and background color, - foreground and background intensity.""" - SetConsoleTextAttribute(stdout_handle, color) diff --git a/Source/mtm/log/LogStreamConsole.py b/Source/mtm/log/LogStreamConsole.py index 29028247..60da23d7 100644 --- a/Source/mtm/log/LogStreamConsole.py +++ b/Source/mtm/log/LogStreamConsole.py @@ -8,7 +8,6 @@ import shutil from mtm.util.Assert import * -import mtm.log.ColorConsole as ColorConsole class AnsiiCodes: BLACK = "\033[1;30m" @@ -49,9 +48,7 @@ def __init__(self, verbose, veryVerbose): self._initColors() def _initColors(self): - self._defaultColors = ColorConsole.get_text_attr() - self._defaultBg = self._defaultColors & 0x0070 - self._defaultFg = self._defaultColors & 0x0007 + print("Colors are not working at the moment on mac.") def log(self, logType, message): @@ -92,38 +89,10 @@ def _getHeadingIndent(self): return self._log.getCurrentNumHeadings() * " " def _output(self, logType, message, stream, useColors): - - stream.write('\n') - stream.write(self._getHeadingIndent()) - - if not useColors or logType == LogType.Info: - stream.write(message) - stream.flush() - else: - ColorConsole.set_text_attr(self._getColorAttrs(logType)) - stream.write(message) - stream.flush() - ColorConsole.set_text_attr(self._defaultColors) - - def _getColorAttrs(self, logType): - if logType == LogType.HeadingStart: - return ColorConsole.FOREGROUND_CYAN | self._defaultBg | ColorConsole.FOREGROUND_INTENSITY - - if logType == LogType.HeadingEnd: - return ColorConsole.FOREGROUND_BLACK | self._defaultBg | ColorConsole.FOREGROUND_INTENSITY - - if logType == LogType.Good: - return ColorConsole.FOREGROUND_GREEN | self._defaultBg | ColorConsole.FOREGROUND_INTENSITY - - if logType == LogType.Warn: - return ColorConsole.FOREGROUND_YELLOW | self._defaultBg | ColorConsole.FOREGROUND_INTENSITY - - if logType == LogType.Error: - return ColorConsole.FOREGROUND_RED | self._defaultBg | ColorConsole.FOREGROUND_INTENSITY - - assertThat(logType == LogType.Debug or logType == LogType.Noise) - return ColorConsole.FOREGROUND_BLACK | self._defaultBg | ColorConsole.FOREGROUND_INTENSITY + stream.write(message) + stream.write('\n') + stream.flush() diff --git a/Source/mtm/log/LogWatcher.py b/Source/mtm/log/LogWatcher.py index a845c47a..f6db5e0c 100644 --- a/Source/mtm/log/LogWatcher.py +++ b/Source/mtm/log/LogWatcher.py @@ -5,7 +5,9 @@ import os import signal import threading -import msvcrt +import tty +import termios #unix only + class LogWatcher: def __init__(self, logPath, logFunc): @@ -82,15 +84,19 @@ def onLog(logStr): log.start() while 1: - if msvcrt.kbhit(): - key = msvcrt.getch().decode('UTF-8') - - if ord(key) == 27: - sys.exit() - - if key == 'c': - os.system('cls') + fd = sys.stdin.fileno() + old_settings = termios.tcgetattr(fd) + try: + tty.setraw(sys.stdin.fileno()) + ch = sys.stdin.read(1) + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) + if ord(ch) == 27: + sys.exit(1) + elif ch == 'c': + exec('clear') time.sleep(0.1) + log.stop() diff --git a/Source/mtm/util/JunctionHelper.py b/Source/mtm/util/JunctionHelper.py index 52898a65..3650c6a7 100644 --- a/Source/mtm/util/JunctionHelper.py +++ b/Source/mtm/util/JunctionHelper.py @@ -2,6 +2,7 @@ import os import mtm.ioc.Container as Container from mtm.ioc.Inject import Inject +from mtm.util.SystemHelper import ProcessErrorCodeException import mtm.ioc.IocAssertions as Assertions import mtm.util.JunctionUtil as JunctionUtil @@ -23,7 +24,7 @@ def removeJunction(self, linkDir): if os.path.isdir(linkDir) and JunctionUtil.islink(linkDir): try: # Use rmdir not python unlink to ensure we don't delete the link source - self._sys.executeShellCommand('rmdir "{0}"'.format(linkDir)) + self._sys.executeShellCommand('rm -r "{0}"'.format(linkDir)) except Exception as e: raise Exception('Failed while attempting to delete junction "{0}":\n{1}'.format(linkDir, str(e))) from e @@ -35,13 +36,17 @@ def makeJunction(self, actualPath, linkPath): actualPath = self._varMgr.expandPath(actualPath) linkPath = self._varMgr.expandPath(linkPath) - assertThat(self._sys.directoryExists(actualPath)) + #if os.path.exists(actualPath): + # self._sys.executeShellCommand("rm -r {0}".format(actualPath)) + if os.path.exists(linkPath): + self._sys.executeShellCommand('rm -r "{0}"'.format(linkPath)) - self._sys.makeMissingDirectoriesInPath(linkPath) + assertThat(not self._sys.directoryExists(linkPath), "These locations should not exist: {0}".format(linkPath)) - self._log.debug('Making junction with actual path ({0}) and new link path ({1})'.format(linkPath, actualPath)) + self._log.debug('Making symlink with actual path ({0}) and new link path ({1})'.format(linkPath, actualPath)) # Note: mklink is a shell command and can't be executed otherwise - self._sys.executeShellCommand('mklink /J "{0}" "{1}"'.format(linkPath, actualPath)) + + self._sys.executeShellCommand('ln -s "{0}" "{1}"'.format(actualPath, linkPath)) def removeJunctionsInDirectory(self, dirPath, recursive): fullDirPath = self._varMgr.expandPath(dirPath) diff --git a/Source/mtm/util/JunctionUtil.py b/Source/mtm/util/JunctionUtil.py index fae12cff..2e379e1b 100644 --- a/Source/mtm/util/JunctionUtil.py +++ b/Source/mtm/util/JunctionUtil.py @@ -1,137 +1,23 @@ -from ctypes import * -from ctypes.wintypes import * - -kernel32 = WinDLL('kernel32') -LPDWORD = POINTER(DWORD) -UCHAR = c_ubyte - -GetFileAttributesW = kernel32.GetFileAttributesW -GetFileAttributesW.restype = DWORD -GetFileAttributesW.argtypes = (LPCWSTR,) #lpFileName In - -INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF -FILE_ATTRIBUTE_REPARSE_POINT = 0x00400 - -CreateFileW = kernel32.CreateFileW -CreateFileW.restype = HANDLE -CreateFileW.argtypes = (LPCWSTR, #lpFileName In - DWORD, #dwDesiredAccess In - DWORD, #dwShareMode In - LPVOID, #lpSecurityAttributes In_opt - DWORD, #dwCreationDisposition In - DWORD, #dwFlagsAndAttributes In - HANDLE) #hTemplateFile In_opt - -CloseHandle = kernel32.CloseHandle -CloseHandle.restype = BOOL -CloseHandle.argtypes = (HANDLE,) #hObject In - -INVALID_HANDLE_VALUE = HANDLE(-1).value -OPEN_EXISTING = 3 -FILE_FLAG_BACKUP_SEMANTICS = 0x02000000 -FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000 - -DeviceIoControl = kernel32.DeviceIoControl -DeviceIoControl.restype = BOOL -DeviceIoControl.argtypes = (HANDLE, #hDevice In - DWORD, #dwIoControlCode In - LPVOID, #lpInBuffer In_opt - DWORD, #nInBufferSize In - LPVOID, #lpOutBuffer Out_opt - DWORD, #nOutBufferSize In - LPDWORD, #lpBytesReturned Out_opt - LPVOID) #lpOverlapped Inout_opt - -FSCTL_GET_REPARSE_POINT = 0x000900A8 -IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003 -IO_REPARSE_TAG_SYMLINK = 0xA000000C -MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 0x4000 - -class GENERIC_REPARSE_BUFFER(Structure): - _fields_ = (('DataBuffer', UCHAR * 1),) - - -class SYMBOLIC_LINK_REPARSE_BUFFER(Structure): - _fields_ = (('SubstituteNameOffset', USHORT), - ('SubstituteNameLength', USHORT), - ('PrintNameOffset', USHORT), - ('PrintNameLength', USHORT), - ('Flags', ULONG), - ('PathBuffer', WCHAR * 1)) - @property - def PrintName(self): - arrayt = WCHAR * (self.PrintNameLength // 2) - offset = type(self).PathBuffer.offset + self.PrintNameOffset - return arrayt.from_address(addressof(self) + offset).value - - -class MOUNT_POINT_REPARSE_BUFFER(Structure): - _fields_ = (('SubstituteNameOffset', USHORT), - ('SubstituteNameLength', USHORT), - ('PrintNameOffset', USHORT), - ('PrintNameLength', USHORT), - ('PathBuffer', WCHAR * 1)) - @property - def PrintName(self): - arrayt = WCHAR * (self.PrintNameLength // 2) - offset = type(self).PathBuffer.offset + self.PrintNameOffset - return arrayt.from_address(addressof(self) + offset).value - - -class REPARSE_DATA_BUFFER(Structure): - class REPARSE_BUFFER(Union): - _fields_ = (('SymbolicLinkReparseBuffer', - SYMBOLIC_LINK_REPARSE_BUFFER), - ('MountPointReparseBuffer', - MOUNT_POINT_REPARSE_BUFFER), - ('GenericReparseBuffer', - GENERIC_REPARSE_BUFFER)) - _fields_ = (('ReparseTag', ULONG), - ('ReparseDataLength', USHORT), - ('Reserved', USHORT), - ('ReparseBuffer', REPARSE_BUFFER)) - _anonymous_ = ('ReparseBuffer',) +import os def islink(path): - result = GetFileAttributesW(path) - if result == INVALID_FILE_ATTRIBUTES: - raise WinError() - return bool(result & FILE_ATTRIBUTE_REPARSE_POINT) + try: + os.readlink(path) + return True + except OSError: + return False + def readlink(path): - reparse_point_handle = CreateFileW(path, - 0, - 0, - None, - OPEN_EXISTING, - FILE_FLAG_OPEN_REPARSE_POINT | - FILE_FLAG_BACKUP_SEMANTICS, - None) - if reparse_point_handle == INVALID_HANDLE_VALUE: - raise WinError() - target_buffer = c_buffer(MAXIMUM_REPARSE_DATA_BUFFER_SIZE) - n_bytes_returned = DWORD() - io_result = DeviceIoControl(reparse_point_handle, - FSCTL_GET_REPARSE_POINT, - None, 0, - target_buffer, len(target_buffer), - byref(n_bytes_returned), - None) - CloseHandle(reparse_point_handle) - if not io_result: - raise WinError() - rdb = REPARSE_DATA_BUFFER.from_buffer(target_buffer) - if rdb.ReparseTag == IO_REPARSE_TAG_SYMLINK: - return rdb.SymbolicLinkReparseBuffer.PrintName - elif rdb.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT: - return rdb.MountPointReparseBuffer.PrintName - raise ValueError("not a link") + try: + link = os.readlink(path) + return link + except OSError: + raise ValueError("not a link") if __name__ == '__main__': - path = "C:/Temp/JunctionTest" - if islink(path): print("yep") else: diff --git a/Source/mtm/util/MiscUtil.py b/Source/mtm/util/MiscUtil.py index 945a477f..84af61f2 100644 --- a/Source/mtm/util/MiscUtil.py +++ b/Source/mtm/util/MiscUtil.py @@ -1,7 +1,6 @@ import subprocess import csv import re -import msvcrt import os import sys import imp @@ -17,21 +16,16 @@ def getExecDirectory(): return os.path.dirname(sys.argv[0]) def confirmChoice(msg): - print('\n' + msg, end="") + valid = {"yes": True, "y": True, "ye": True, "no": False, "n": False} - while True: - if msvcrt.kbhit(): - choice = msvcrt.getch().decode("utf-8") - - if choice == 'y': - return True + print('/n'+msg, end="") - if choice == 'n': - return False - - if choice == '\x03': - return False + choice = input().lower() + while True: + if choice in valid: + return valid[choice] + else: print('Invalid selection "%s".' % choice) def tryKillAdbExe(sysManager): diff --git a/Source/mtm/util/SystemHelper.py b/Source/mtm/util/SystemHelper.py index f972b778..31997fd9 100644 --- a/Source/mtm/util/SystemHelper.py +++ b/Source/mtm/util/SystemHelper.py @@ -1,15 +1,10 @@ -from mtm.log.Logger import Logger -from mtm.util.VarManager import VarManager -from mtm.util.ProcessRunner import ProcessRunner from mtm.util.ProcessRunner import ResultType import string import fnmatch from mtm.util.Assert import * -import mtm.ioc.Container as Container from mtm.ioc.Inject import Inject -from mtm.ioc.Inject import InjectOptional import mtm.util.JunctionUtil as JunctionUtil import time @@ -246,6 +241,7 @@ def deleteEmptyDirectoriesUnder(self, dirPath): if len(files) == 0: self._log.debug("Removing empty folder '%s'" % dirPath) os.rmdir(dirPath) + exec('rm -r "{0}"'.format(dirPath)) numDirsDeleted += 1 metaFilePath = dirPath + '/../' + os.path.basename(dirPath) + '.meta' @@ -260,7 +256,7 @@ def fileExists(self, path): return os.path.isfile(self._varManager.expand(path)) def directoryExists(self, dirPath): - return os.path.exists(self._varManager.expand(dirPath)) + return os.path.exists(self._varManager.expand(dirPath)) or os.path.islink(self._varManager.expand(dirPath)) def copyDirectory(self, fromPath, toPath): fromPath = self._varManager.expand(fromPath) diff --git a/Source/mtm/util/UnityHelper.py b/Source/mtm/util/UnityHelper.py index f1284f3b..925b77a6 100644 --- a/Source/mtm/util/UnityHelper.py +++ b/Source/mtm/util/UnityHelper.py @@ -3,18 +3,15 @@ from mtm.log.LogWatcher import LogWatcher -import mtm.ioc.Container as Container from mtm.ioc.Inject import Inject -import mtm.ioc.IocAssertions as Assertions from mtm.util.Assert import * -import mtm.util.MiscUtil as MiscUtil import mtm.util.PlatformUtil as PlatformUtil from mtm.util.Platforms import Platforms from mtm.util.SystemHelper import ProcessErrorCodeException -UnityLogFileLocation = os.getenv('localappdata') + '\\Unity\\Editor\\Editor.log' +UnityLogFileLocation = os.getenv('HOME') + '/Unity/Editor/Editor.log' #UnityLogFileLocation = '{Modest3dDir}/Modest3DLog.txt' class UnityReturnedErrorCodeException(Exception): @@ -51,7 +48,8 @@ def runEditorFunction(self, projectName, platform, editorCommand, batchMode = Tr def openUnity(self, projectName, platform): with self._log.heading('Opening Unity'): projectPath = self._sys.canonicalizePath("[UnityProjectsDir]/{0}/{1}-{2}".format(projectName, self._commonSettings.getShortProjectName(projectName), PlatformUtil.toPlatformFolderName(platform))) - self._sys.executeNoWait('"[UnityExePath]" -buildTarget {0} -projectPath "{1}"'.format(self._getBuildTargetArg(platform), projectPath)) + print('/Applications/Unity/Unity.app/Contents/MacOS/Unity -buildTarget {0} -projectPath "{1}"'.format(self._getBuildTargetArg(platform), projectPath)) + self._sys.executeShellCommand('/Applications/Unity/Unity.app/Contents/MacOS/Unity -buildTarget {0} -projectPath "{1}"'.format(self._getBuildTargetArg(platform), projectPath)) def _getBuildTargetArg(self, platform): diff --git a/Source/mtm/util/tests/TestSystemHelper.py b/Source/mtm/util/tests/TestSystemHelper.py index c9e63a53..0fa4d997 100644 --- a/Source/mtm/util/tests/TestSystemHelper.py +++ b/Source/mtm/util/tests/TestSystemHelper.py @@ -3,8 +3,6 @@ import unittest import mtm.ioc.Container as Container -from mtm.ioc.Inject import Inject -import mtm.ioc.IocAssertions as Assertions from mtm.util.VarManager import VarManager from mtm.util.SystemHelper import SystemHelper diff --git a/Source/prj/main/PackageManager.py b/Source/prj/main/PackageManager.py index a35c6cbf..2f57c2bb 100644 --- a/Source/prj/main/PackageManager.py +++ b/Source/prj/main/PackageManager.py @@ -1,12 +1,6 @@ import os -from mtm.util.VarManager import VarManager -from mtm.log.Logger import Logger -from mtm.util.SystemHelper import SystemHelper -import mtm.util.JunctionUtil as JunctionUtil -import mtm.util.Util as Util - from prj.main.ProjectSchemaLoader import FolderTypes from mtm.util.Platforms import Platforms @@ -19,12 +13,9 @@ from mtm.util.Assert import * from prj.reg.PackageInfo import PackageInfo, PackageFolderInfo, PackageInstallInfo -from datetime import datetime import mtm.util.YamlSerializer as YamlSerializer -import mtm.ioc.Container as Container from mtm.ioc.Inject import Inject from mtm.ioc.Inject import InjectMany -import mtm.ioc.IocAssertions as Assertions InstallInfoFileName = 'ProjenyInstall.yaml' @@ -150,7 +141,6 @@ def updateProjectJunctions(self, projectName, platform): self.setPathsForProjectPlatform(projectName, platform) schema = self._schemaLoader.loadSchema(projectName, platform) self._updateDirLinksForSchema(schema) - self._checkForVersionControlIgnore() self._log.good('Finished updating packages for project "{0}"'.format(schema.name)) @@ -327,6 +317,7 @@ def _updateDirLinksForSchema(self, schema): else: dllOutPath = '[PluginsDir]/Projeny/Editor/Projeny.dll' + #This place may still be an issue self._sys.copyFile('[ProjenyUnityEditorDllPath]', dllOutPath) self._sys.copyFile('[ProjenyUnityEditorDllMetaFilePath]', dllOutPath + '.meta') diff --git a/Source/prj/main/Prj.py b/Source/prj/main/Prj.py index d121da88..6e968a41 100644 --- a/Source/prj/main/Prj.py +++ b/Source/prj/main/Prj.py @@ -5,7 +5,6 @@ import argparse import mtm.util.MiscUtil as MiscUtil -import mtm.util.PlatformUtil as PlatformUtil from mtm.config.YamlConfigLoader import loadYamlFilesThatExist from mtm.config.Config import Config @@ -39,11 +38,9 @@ from mtm.util.Assert import * -from mtm.util.PlatformUtil import Platforms from prj.main.PackageManager import PackageManager import mtm.ioc.Container as Container -from mtm.ioc.Inject import Inject from mtm.util.UnityHelper import UnityHelper @@ -104,7 +101,8 @@ def _getProjenyDir(): scriptDir = os.path.dirname(os.path.realpath(__file__)) return os.path.join(scriptDir, '../../..') - # This works for both exe builds (Bin/Prj/Data/Prj.exe) and running from source (Source/prj/_main/Prj.py) by coincidence + # This works for both exe builds (Bin/Prj/Data/Prj.exe) and running from source (Source/prj/_main/Prj.py) by + # coincidence return os.path.join(MiscUtil.getExecDirectory(), '../../..') def _getExtraUserConfigPaths(): @@ -191,7 +189,7 @@ def _getParentDirsAndSelf(dirPath): parentDir = os.path.dirname(parentDir) def findMainConfigPath(): - for dirPath in _getParentDirsAndSelf(os.getcwd()): + for dirPath in _getParentDirsAndSelf(sys.argv[1]): configPath = os.path.join(dirPath, ConfigFileName) if os.path.isfile(configPath): @@ -244,21 +242,22 @@ def _createConfig(): - '[ProjectRoot]/Packages' """) + def _main(): # Here we split out some functionality into various methods # so that other python code can make use of them - # if they want to extend projeny + # if they want to extend Projeny parser = argparse.ArgumentParser(description='Unity Package Manager') addArguments(parser) - argv = sys.argv[1:] + argv = sys.argv[2:] # If it's 2 then it only has the -cfg param if len(argv) == 0: parser.print_help() sys.exit(2) - args = parser.parse_args(sys.argv[1:]) + args = parser.parse_args(sys.argv[2:]) Container.bind('LogStream').toSingle(LogStreamFile) Container.bind('LogStream').toSingle(LogStreamConsole, args.verbose, args.veryVerbose) diff --git a/Source/prj/main/ProjectSchemaLoader.py b/Source/prj/main/ProjectSchemaLoader.py index 982e2840..1d81169e 100644 --- a/Source/prj/main/ProjectSchemaLoader.py +++ b/Source/prj/main/ProjectSchemaLoader.py @@ -157,6 +157,7 @@ def _getAllPackageInfos(self, projectConfig, platform): packageName = packageRef.name packageDir = None + for packageFolder in projectConfig.packageFolders: candidatePackageDir = os.path.join(packageFolder, packageName) diff --git a/Source/prj/main/ProjenyVisualStudioHelper.py b/Source/prj/main/ProjenyVisualStudioHelper.py index 514b5c1b..a10d835a 100644 --- a/Source/prj/main/ProjenyVisualStudioHelper.py +++ b/Source/prj/main/ProjenyVisualStudioHelper.py @@ -1,16 +1,4 @@ - -import mtm.ioc.Container as Container from mtm.ioc.Inject import Inject -from mtm.ioc.Inject import InjectMany -import mtm.ioc.IocAssertions as Assertions -import mtm.util.MiscUtil as MiscUtil - -from mtm.util.CommonSettings import ConfigFileName - -import win32api -import win32com.client - -from mtm.util.Assert import * class ProjenyVisualStudioHelper: _vsHelper = Inject('VisualStudioHelper') diff --git a/Source/prj/main/VisualStudioHelper.py b/Source/prj/main/VisualStudioHelper.py index 7bb7370c..7db01320 100644 --- a/Source/prj/main/VisualStudioHelper.py +++ b/Source/prj/main/VisualStudioHelper.py @@ -7,8 +7,6 @@ from mtm.util.CommonSettings import ConfigFileName -import win32api -import win32com.client from mtm.util.Assert import * @@ -32,30 +30,25 @@ def openFile(self, filePath, lineNo, solutionPath): self.openVisualStudioSolution(solutionPath, filePath) def openFileInExistingVisualStudioInstance(self, filePath, lineNo): - try: - dte = win32com.client.GetActiveObject("VisualStudio.DTE.12.0") - - dte.MainWindow.Activate - dte.ItemOperations.OpenFile(self._sys.canonicalizePath(filePath)) - dte.ActiveDocument.Selection.MoveToLineAndOffset(lineNo, 1) - except Exception as error: - raise Exception("COM Error. This is often triggered when given a bad line number. Details: {0}".format(win32api.FormatMessage(error.excepinfo[5]))) + self._sys.executeShellCommand("open {0}".format(self._sys.canonicalizePath(filePath))) def openVisualStudioSolution(self, solutionPath, filePath = None): - if self._varMgr.hasKey('VisualStudioIdePath'): - assertThat(self._sys.fileExists('[VisualStudioIdePath]'), - "Cannot find path to visual studio. Expected to find it at '{0}'".format(self._varMgr.expand('[VisualStudioIdePath]'))) - - if solutionPath == None: - self._sys.executeNoWait('"[VisualStudioIdePath]" {0}'.format(self._sys.canonicalizePath(filePath) if filePath else "")) - else: - solutionPath = self._sys.canonicalizePath(solutionPath) - self._sys.executeNoWait('"[VisualStudioIdePath]" {0} {1}'.format(solutionPath, self._sys.canonicalizePath(filePath) if filePath else "")) - else: - assertThat(filePath == None, - "Path to visual studio has not been defined. Please set within one of your {0} files. See documentation for details.", ConfigFileName) - self._sys.executeShellCommand(solutionPath, None, False) + # if self._varMgr.hasKey('VisualStudioIdePath'): + # assertThat(self._sys.fileExists('[VisualStudioIdePath]'), + # "Cannot find path to visual studio. Expected to find it at '{0}'".format(self._varMgr.expand('[VisualStudioIdePath]'))) + # + # if solutionPath == None: + # self._sys.executeNoWait('"[VisualStudioIdePath]" {0}'.format(self._sys.canonicalizePath(filePath) if filePath else "")) + # else: + # solutionPath = self._sys.canonicalizePath(solutionPath) + # self._sys.executeNoWait('"[VisualStudioIdePath]" {0} {1}'.format(solutionPath, self._sys.canonicalizePath(filePath) if filePath else "")) + # else: + # assertThat(filePath == None, + # "Path to visual studio has not been defined. Please set within one of your {0} files. See documentation for details.", ConfigFileName) + # self._sys.executeShellCommand(solutionPath, None, False) + + self._sys.executeShellCommand("open {0}".format(self._sys.canonicalizePath(solutionPath))) def buildVisualStudioProject(self, solutionPath, buildConfig): solutionPath = self._varMgr.expand(solutionPath) diff --git a/Source/prj/main/VisualStudioSolutionGenerator.py b/Source/prj/main/VisualStudioSolutionGenerator.py index 9a7db2be..e52b1c59 100644 --- a/Source/prj/main/VisualStudioSolutionGenerator.py +++ b/Source/prj/main/VisualStudioSolutionGenerator.py @@ -69,7 +69,7 @@ def _getUnityProjectReferencesItems(self, root): hintPathElem = children[0] assertThat(hintPathElem.tag == '{0}HintPath'.format(NsPrefix)) - hintPath = hintPathElem.text.replace('/', '\\') + hintPath = hintPathElem.text if hintPath: if not os.path.isabs(hintPath): diff --git a/Source/prj/reg/UnityPackageAnalyzer.py b/Source/prj/reg/UnityPackageAnalyzer.py index f88b3bb7..7bc51095 100644 --- a/Source/prj/reg/UnityPackageAnalyzer.py +++ b/Source/prj/reg/UnityPackageAnalyzer.py @@ -130,7 +130,7 @@ def _tryGetAssetStoreInfoFromHeader(self, unityPackagePath): Container.bind('Config').toSingle(Config, []) Container.bind('Logger').toSingle(Logger) - Container.bind('VarManager').toSingle(VarManager, { 'UnityExePath': "C:/Program Files/Unity/Editor/Unity.exe" }) + Container.bind('VarManager').toSingle(VarManager, { 'UnityExePath': "/Applications/Unity/Unity.app"}) #Container.bind('LogStream').toSingle(LogStreamConsole, True, True) Container.bind('SystemHelper').toSingle(SystemHelper) Container.bind('ProcessRunner').toSingle(ProcessRunner) diff --git a/Source/prj/reg/UnityPackageExtractor.py b/Source/prj/reg/UnityPackageExtractor.py index 188b21bc..3a25f29f 100644 --- a/Source/prj/reg/UnityPackageExtractor.py +++ b/Source/prj/reg/UnityPackageExtractor.py @@ -107,7 +107,7 @@ def _chooseDirToCopy(self, startDir): if __name__ == '__main__': Container.bind('Config').toSingle(Config, []) Container.bind('Logger').toSingle(Logger) - Container.bind('VarManager').toSingle(VarManager, { 'UnityExePath': "C:/Program Files/Unity/Editor/Unity.exe" }) + Container.bind('VarManager').toSingle(VarManager, { 'UnityExePath': "/Applications/Unity/Unity.app" }) Container.bind('LogStream').toSingle(LogStreamConsole, True, True) Container.bind('SystemHelper').toSingle(SystemHelper) Container.bind('ProcessRunner').toSingle(ProcessRunner) diff --git a/UnityPlugin/Projeny/Internal/Assert.cs b/UnityPlugin/Projeny/Internal/Assert.cs index c5dc92f5..9f0fd50e 100644 --- a/UnityPlugin/Projeny/Internal/Assert.cs +++ b/UnityPlugin/Projeny/Internal/Assert.cs @@ -6,207 +6,193 @@ namespace Projeny.Internal { - public static class Assert - { - public static void That(bool condition) - { - if (!condition) - { - Throw("Assert hit!"); - } - } - - public static void IsType(object obj) - { - IsType(obj, ""); - } - - public static void IsType(object obj, string message) - { - if (!(obj is T)) - { - Throw("Assert Hit! Wrong type found. Expected '"+ typeof(T).Name + "' but found '" + obj.GetType().Name + "'. " + message); - } - } - - // Use AssertEquals to get better error output (with values) - public static void IsEqual(object left, object right) - { - IsEqual(left, right, ""); - } - - public static void Throws(Action action) - { - Throws(action); - } - - public static void Throws(Action action) + public static class Assert + { + public static void That (bool condition, params Object[] args) + { + + if (!condition) { + if (args.Length > 0) { + string msg = args [0].ToString (); + Throw (msg); + + } + Throw ("Assert hit!"); + } + } + + public static void IsType (object obj) + { + IsType (obj, ""); + } + + public static void IsType (object obj, string message) + { + if (!(obj is T)) { + Throw ("Assert Hit! Wrong type found. Expected '" + typeof(T).Name + "' but found '" + obj.GetType ().Name + "'. " + message); + } + } + + // Use AssertEquals to get better error output (with values) + public static void IsEqual (object left, object right) + { + IsEqual (left, right, ""); + } + + public static void Throws (Action action) + { + Throws (action); + } + + public static void Throws (Action action) where TException : Exception - { - try - { - action(); - } - catch (TException) - { - return; - } - - Throw(string.Format("Expected to receive exception of type '{0}' but nothing was thrown", typeof(TException).Name)); - } - - // Use AssertEquals to get better error output (with values) - public static void IsEqual(object left, object right, Func messageGenerator) - { - if (!object.Equals(left, right)) - { - left = left ?? ""; - right = right ?? ""; - Throw("Assert Hit! Expected '" + right.ToString() + "' but found '" + left.ToString() + "'. " + messageGenerator()); - } - } - - // Use AssertEquals to get better error output (with values) - public static void IsEqual(object left, object right, string message) - { - if (!object.Equals(left, right)) - { - left = left ?? ""; - right = right ?? ""; - Throw("Assert Hit! Expected '" + right.ToString() + "' but found '" + left.ToString() + "'. " + message); - } - } - - // Use Assert.IsNotEqual to get better error output (with values) - public static void IsNotEqual(object left, object right) - { - IsNotEqual(left, right, ""); - } - - // Use Assert.IsNotEqual to get better error output (with values) - public static void IsNotEqual(object left, object right, Func messageGenerator) - { - if(object.Equals(left, right)) - { - left = left ?? ""; - right = right ?? ""; - Throw("Assert Hit! Expected '" + right.ToString() + "' to differ from '" + left.ToString() + "'. " + messageGenerator()); - } - } - - public static void IsNull(object val) - { - if (val != null) - { - Throw("Assert Hit! Expected null pointer but instead found '" + val.ToString() + "'"); - } - } - - public static void IsNotNull(object val) - { - if (val == null) - { - Throw("Assert Hit! Found null pointer when value was expected"); - } - } - - public static void IsNotNull(object val, string message) - { - if (val == null) - { - Throw("Assert Hit! Found null pointer when value was expected. " + message); - } - } - - public static void IsNull(object val, string message, params object[] parameters) - { - if (val != null) - { - Throw("Assert Hit! Expected null pointer but instead found '" + val.ToString() + "': " + FormatString(message, parameters)); - } - } - - public static void IsNotNull(object val, string message, params object[] parameters) - { - if (val == null) - { - Throw("Assert Hit! Found null pointer when value was expected. " + FormatString(message, parameters)); - } - } - - // Use Assert.IsNotEqual to get better error output (with values) - public static void IsNotEqual(object left, object right, string message) - { - if (object.Equals(left, right)) - { - left = left ?? ""; - right = right ?? ""; - Throw("Assert Hit! Expected '" + right.ToString() + "' to differ from '" + left.ToString() + "'. " + message); - } - } - - // Pass a function instead of a string for cases that involve a lot of processing to generate a string - // This way the processing only occurs when the assert fails - public static void That(bool condition, Func messageGenerator) - { - if (!condition) - { - Throw("Assert hit! " + messageGenerator()); - } - } - - public static void That( - bool condition, string message, params object[] parameters) - { - if (!condition) - { - Throw("Assert hit! " + FormatString(message, parameters)); - } - } - - public static void Throw() - { - throw new Exception("Assert Hit!"); - } - - public static void Throw(string message) - { - throw new Exception(message); - } - - public static void Throw(string message, params object[] parameters) - { - throw new Exception( - FormatString(message, parameters)); - } - - static string FormatString(string format, params object[] parameters) - { - // doin this funky loop to ensure nulls are replaced with "NULL" - // and that the original parameters array will not be modified - if (parameters != null && parameters.Length > 0) - { - object[] paramToUse = parameters; - - foreach (object cur in parameters) - { - if (cur == null) - { - paramToUse = new object[parameters.Length]; - - for (int i = 0; i < parameters.Length; ++i) - { - paramToUse[i] = parameters[i] ?? "NULL"; - } - - break; - } - } - - format = string.Format(format, paramToUse); - } - - return format; - } - } + { + try { + action (); + } catch (TException) { + return; + } + + Throw (string.Format ("Expected to receive exception of type '{0}' but nothing was thrown", typeof(TException).Name)); + } + + // Use AssertEquals to get better error output (with values) + public static void IsEqual (object left, object right, Func messageGenerator) + { + if (!object.Equals (left, right)) { + left = left ?? ""; + right = right ?? ""; + Throw ("Assert Hit! Expected '" + right.ToString () + "' but found '" + left.ToString () + "'. " + messageGenerator ()); + } + } + + // Use AssertEquals to get better error output (with values) + public static void IsEqual (object left, object right, string message) + { + if (!object.Equals (left, right)) { + left = left ?? ""; + right = right ?? ""; + Throw ("Assert Hit! Expected '" + right.ToString () + "' but found '" + left.ToString () + "'. " + message); + } + } + + // Use Assert.IsNotEqual to get better error output (with values) + public static void IsNotEqual (object left, object right) + { + IsNotEqual (left, right, ""); + } + + // Use Assert.IsNotEqual to get better error output (with values) + public static void IsNotEqual (object left, object right, Func messageGenerator) + { + if (object.Equals (left, right)) { + left = left ?? ""; + right = right ?? ""; + Throw ("Assert Hit! Expected '" + right.ToString () + "' to differ from '" + left.ToString () + "'. " + messageGenerator ()); + } + } + + public static void IsNull (object val) + { + if (val != null) { + Throw ("Assert Hit! Expected null pointer but instead found '" + val.ToString () + "'"); + } + } + + public static void IsNotNull (object val) + { + if (val == null) { + Throw ("Assert Hit! Found null pointer when value was expected"); + } + } + + public static void IsNotNull (object val, string message) + { + if (val == null) { + Throw ("Assert Hit! Found null pointer when value was expected. " + message); + } + } + + public static void IsNull (object val, string message, params object[] parameters) + { + if (val != null) { + Throw ("Assert Hit! Expected null pointer but instead found '" + val.ToString () + "': " + FormatString (message, parameters)); + } + } + + public static void IsNotNull (object val, string message, params object[] parameters) + { + if (val == null) { + Throw ("Assert Hit! Found null pointer when value was expected. " + FormatString (message, parameters)); + } + } + + // Use Assert.IsNotEqual to get better error output (with values) + public static void IsNotEqual (object left, object right, string message) + { + if (object.Equals (left, right)) { + left = left ?? ""; + right = right ?? ""; + Throw ("Assert Hit! Expected '" + right.ToString () + "' to differ from '" + left.ToString () + "'. " + message); + } + } + + // Pass a function instead of a string for cases that involve a lot of processing to generate a string + // This way the processing only occurs when the assert fails + public static void That (bool condition, Func messageGenerator) + { + if (!condition) { + Throw ("Assert hit! " + messageGenerator ()); + } + } + + public static void That ( + bool condition, string message, params object[] parameters) + { + if (!condition) { + Throw ("Assert hit! " + FormatString (message, parameters)); + } + } + + public static void Throw () + { + throw new Exception ("Assert Hit!"); + } + + public static void Throw (string message) + { + throw new Exception (message); + } + + public static void Throw (string message, params object[] parameters) + { + throw new Exception ( + FormatString (message, parameters)); + } + + static string FormatString (string format, params object[] parameters) + { + // doin this funky loop to ensure nulls are replaced with "NULL" + // and that the original parameters array will not be modified + if (parameters != null && parameters.Length > 0) { + object[] paramToUse = parameters; + + foreach (object cur in parameters) { + if (cur == null) { + paramToUse = new object[parameters.Length]; + + for (int i = 0; i < parameters.Length; ++i) { + paramToUse [i] = parameters [i] ?? "NULL"; + } + + break; + } + } + + format = string.Format (format, paramToUse); + } + + return format; + } + } } diff --git a/UnityPlugin/Projeny/Internal/JunctionPoint.cs b/UnityPlugin/Projeny/Internal/JunctionPoint.cs index c408d544..8afee56a 100644 --- a/UnityPlugin/Projeny/Internal/JunctionPoint.cs +++ b/UnityPlugin/Projeny/Internal/JunctionPoint.cs @@ -8,401 +8,385 @@ // http://www.codeproject.com/Articles/15633/Manipulating-NTFS-Junction-Points-in-NET namespace Projeny.Internal { - /// - /// Provides access to NTFS junction points in .Net. - /// - public static class JunctionPoint - { - /// - /// The file or directory is not a reparse point. - /// - private const int ERROR_NOT_A_REPARSE_POINT = 4390; - - /// - /// The reparse point attribute cannot be set because it conflicts with an existing attribute. - /// - private const int ERROR_REPARSE_ATTRIBUTE_CONFLICT = 4391; - - /// - /// The data present in the reparse point buffer is invalid. - /// - private const int ERROR_INVALID_REPARSE_DATA = 4392; - - /// - /// The tag present in the reparse point buffer is invalid. - /// - private const int ERROR_REPARSE_TAG_INVALID = 4393; - - /// - /// There is a mismatch between the tag specified in the request and the tag present in the reparse point. - /// - private const int ERROR_REPARSE_TAG_MISMATCH = 4394; - - /// - /// Command to set the reparse point data block. - /// - private const int FSCTL_SET_REPARSE_POINT = 0x000900A4; - - /// - /// Command to get the reparse point data block. - /// - private const int FSCTL_GET_REPARSE_POINT = 0x000900A8; - - /// - /// Command to delete the reparse point data base. - /// - private const int FSCTL_DELETE_REPARSE_POINT = 0x000900AC; - - /// - /// Reparse point tag used to identify mount points and junction points. - /// - private const uint IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003; - - /// - /// This prefix indicates to NTFS that the path is to be treated as a non-interpreted - /// path in the virtual file system. - /// - private const string NonInterpretedPathPrefix = @"\??\"; - - [Flags] - private enum EFileAccess : uint - { - GenericRead = 0x80000000, - GenericWrite = 0x40000000, - GenericExecute = 0x20000000, - GenericAll = 0x10000000, - } - - [Flags] - private enum EFileShare : uint - { - None = 0x00000000, - Read = 0x00000001, - Write = 0x00000002, - Delete = 0x00000004, - } - - private enum ECreationDisposition : uint - { - New = 1, - CreateAlways = 2, - OpenExisting = 3, - OpenAlways = 4, - TruncateExisting = 5, - } - - [Flags] - private enum EFileAttributes : uint - { - Readonly = 0x00000001, - Hidden = 0x00000002, - System = 0x00000004, - Directory = 0x00000010, - Archive = 0x00000020, - Device = 0x00000040, - Normal = 0x00000080, - Temporary = 0x00000100, - SparseFile = 0x00000200, - ReparsePoint = 0x00000400, - Compressed = 0x00000800, - Offline = 0x00001000, - NotContentIndexed = 0x00002000, - Encrypted = 0x00004000, - Write_Through = 0x80000000, - Overlapped = 0x40000000, - NoBuffering = 0x20000000, - RandomAccess = 0x10000000, - SequentialScan = 0x08000000, - DeleteOnClose = 0x04000000, - BackupSemantics = 0x02000000, - PosixSemantics = 0x01000000, - OpenReparsePoint = 0x00200000, - OpenNoRecall = 0x00100000, - FirstPipeInstance = 0x00080000 - } - - [StructLayout(LayoutKind.Sequential)] - private struct REPARSE_DATA_BUFFER - { - /// - /// Reparse point tag. Must be a Microsoft reparse point tag. - /// - public uint ReparseTag; - - /// - /// Size, in bytes, of the data after the Reserved member. This can be calculated by: - /// (4 * sizeof(ushort)) + SubstituteNameLength + PrintNameLength + - /// (namesAreNullTerminated ? 2 * sizeof(char) : 0); - /// - public ushort ReparseDataLength; - - /// - /// Reserved; do not use. - /// - public ushort Reserved; - - /// - /// Offset, in bytes, of the substitute name string in the PathBuffer array. - /// - public ushort SubstituteNameOffset; - - /// - /// Length, in bytes, of the substitute name string. If this string is null-terminated, - /// SubstituteNameLength does not include space for the null character. - /// - public ushort SubstituteNameLength; - - /// - /// Offset, in bytes, of the print name string in the PathBuffer array. - /// - public ushort PrintNameOffset; - - /// - /// Length, in bytes, of the print name string. If this string is null-terminated, - /// PrintNameLength does not include space for the null character. - /// - public ushort PrintNameLength; - - /// - /// A buffer containing the unicode-encoded path string. The path string contains - /// the substitute name string and print name string. - /// - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x3FF0)] - public byte[] PathBuffer; - } - - [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] - private static extern bool DeviceIoControl(IntPtr hDevice, uint dwIoControlCode, - IntPtr InBuffer, int nInBufferSize, - IntPtr OutBuffer, int nOutBufferSize, - out int pBytesReturned, IntPtr lpOverlapped); - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern IntPtr CreateFile( - string lpFileName, - EFileAccess dwDesiredAccess, - EFileShare dwShareMode, - IntPtr lpSecurityAttributes, - ECreationDisposition dwCreationDisposition, - EFileAttributes dwFlagsAndAttributes, - IntPtr hTemplateFile); - - /// - /// Creates a junction point from the specified directory to the specified target directory. - /// - /// - /// Only works on NTFS. - /// - /// The junction point path - /// The target directory - /// If true overwrites an existing reparse point or empty directory - /// Thrown when the junction point could not be created or when - /// an existing directory was found and if false - public static void Create(string junctionPoint, string targetDir, bool overwrite) - { - targetDir = Path.GetFullPath(targetDir); - - if (!Directory.Exists(targetDir)) - throw new IOException("Target path does not exist or is not a directory."); - - if (Directory.Exists(junctionPoint)) - { - if (!overwrite) - throw new IOException("Directory already exists and overwrite parameter is false."); - } - else - { - Directory.CreateDirectory(junctionPoint); - } - - using (SafeFileHandle handle = OpenReparsePoint(junctionPoint, EFileAccess.GenericWrite)) - { - byte[] targetDirBytes = Encoding.Unicode.GetBytes(NonInterpretedPathPrefix + Path.GetFullPath(targetDir)); - - REPARSE_DATA_BUFFER reparseDataBuffer = new REPARSE_DATA_BUFFER(); - - reparseDataBuffer.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; - reparseDataBuffer.ReparseDataLength = (ushort)(targetDirBytes.Length + 12); - reparseDataBuffer.SubstituteNameOffset = 0; - reparseDataBuffer.SubstituteNameLength = (ushort)targetDirBytes.Length; - reparseDataBuffer.PrintNameOffset = (ushort)(targetDirBytes.Length + 2); - reparseDataBuffer.PrintNameLength = 0; - reparseDataBuffer.PathBuffer = new byte[0x3ff0]; - Array.Copy(targetDirBytes, reparseDataBuffer.PathBuffer, targetDirBytes.Length); - - int inBufferSize = Marshal.SizeOf(reparseDataBuffer); - IntPtr inBuffer = Marshal.AllocHGlobal(inBufferSize); - - try - { - Marshal.StructureToPtr(reparseDataBuffer, inBuffer, false); - - int bytesReturned; - bool result = DeviceIoControl(handle.DangerousGetHandle(), FSCTL_SET_REPARSE_POINT, - inBuffer, targetDirBytes.Length + 20, IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero); - - if (!result) - ThrowLastWin32Error("Unable to create junction point."); - } - finally - { - Marshal.FreeHGlobal(inBuffer); - } - } - } - - /// - /// Deletes a junction point at the specified source directory along with the directory itself. - /// Does nothing if the junction point does not exist. - /// - /// - /// Only works on NTFS. - /// - /// The junction point path - public static void Delete(string junctionPoint) - { - if (!Directory.Exists(junctionPoint)) - { - if (File.Exists(junctionPoint)) - throw new IOException("Path is not a junction point."); - - return; - } - - using (SafeFileHandle handle = OpenReparsePoint(junctionPoint, EFileAccess.GenericWrite)) - { - REPARSE_DATA_BUFFER reparseDataBuffer = new REPARSE_DATA_BUFFER(); - - reparseDataBuffer.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; - reparseDataBuffer.ReparseDataLength = 0; - reparseDataBuffer.PathBuffer = new byte[0x3ff0]; - - int inBufferSize = Marshal.SizeOf(reparseDataBuffer); - IntPtr inBuffer = Marshal.AllocHGlobal(inBufferSize); - try - { - Marshal.StructureToPtr(reparseDataBuffer, inBuffer, false); - - int bytesReturned; - bool result = DeviceIoControl(handle.DangerousGetHandle(), FSCTL_DELETE_REPARSE_POINT, - inBuffer, 8, IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero); - - if (!result) - ThrowLastWin32Error("Unable to delete junction point."); - } - finally - { - Marshal.FreeHGlobal(inBuffer); - } - - try - { - Directory.Delete(junctionPoint); - } - catch (IOException ex) - { - throw new IOException("Unable to delete junction point.", ex); - } - } - } - - /// - /// Determines whether the specified path exists and refers to a junction point. - /// - /// The junction point path - /// True if the specified path represents a junction point - /// Thrown if the specified path is invalid - /// or some other error occurs - public static bool Exists(string path) - { - if (! Directory.Exists(path)) - return false; - - using (SafeFileHandle handle = OpenReparsePoint(path, EFileAccess.GenericRead)) - { - string target = InternalGetTarget(handle); - return target != null; - } - } - - /// - /// Gets the target of the specified junction point. - /// - /// - /// Only works on NTFS. - /// - /// The junction point path - /// The target of the junction point - /// Thrown when the specified path does not - /// exist, is invalid, is not a junction point, or some other error occurs - public static string GetTarget(string junctionPoint) - { - using (SafeFileHandle handle = OpenReparsePoint(junctionPoint, EFileAccess.GenericRead)) - { - string target = InternalGetTarget(handle); - if (target == null) - throw new IOException("Path is not a junction point."); - - return target; - } - } - - private static string InternalGetTarget(SafeFileHandle handle) - { - int outBufferSize = Marshal.SizeOf(typeof(REPARSE_DATA_BUFFER)); - IntPtr outBuffer = Marshal.AllocHGlobal(outBufferSize); - - try - { - int bytesReturned; - bool result = DeviceIoControl(handle.DangerousGetHandle(), FSCTL_GET_REPARSE_POINT, - IntPtr.Zero, 0, outBuffer, outBufferSize, out bytesReturned, IntPtr.Zero); - - if (!result) - { - int error = Marshal.GetLastWin32Error(); - if (error == ERROR_NOT_A_REPARSE_POINT) - return null; - + /// + /// Provides access to NTFS junction points in .Net. + /// + public static class JunctionPoint + { + /// + /// The file or directory is not a reparse point. + /// + private const int ERROR_NOT_A_REPARSE_POINT = 4390; + + /// + /// The reparse point attribute cannot be set because it conflicts with an existing attribute. + /// + private const int ERROR_REPARSE_ATTRIBUTE_CONFLICT = 4391; + + /// + /// The data present in the reparse point buffer is invalid. + /// + private const int ERROR_INVALID_REPARSE_DATA = 4392; + + /// + /// The tag present in the reparse point buffer is invalid. + /// + private const int ERROR_REPARSE_TAG_INVALID = 4393; + + /// + /// There is a mismatch between the tag specified in the request and the tag present in the reparse point. + /// + private const int ERROR_REPARSE_TAG_MISMATCH = 4394; + + /// + /// Command to set the reparse point data block. + /// + private const int FSCTL_SET_REPARSE_POINT = 0x000900A4; + + /// + /// Command to get the reparse point data block. + /// + private const int FSCTL_GET_REPARSE_POINT = 0x000900A8; + + /// + /// Command to delete the reparse point data base. + /// + private const int FSCTL_DELETE_REPARSE_POINT = 0x000900AC; + + /// + /// Reparse point tag used to identify mount points and junction points. + /// + private const uint IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003; + + /// + /// This prefix indicates to NTFS that the path is to be treated as a non-interpreted + /// path in the virtual file system. + /// + private const string NonInterpretedPathPrefix = @"\??\"; + + [Flags] + private enum EFileAccess : uint + { + GenericRead = 0x80000000, + GenericWrite = 0x40000000, + GenericExecute = 0x20000000, + GenericAll = 0x10000000, + } + + [Flags] + private enum EFileShare : uint + { + None = 0x00000000, + Read = 0x00000001, + Write = 0x00000002, + Delete = 0x00000004, + } + + private enum ECreationDisposition : uint + { + New = 1, + CreateAlways = 2, + OpenExisting = 3, + OpenAlways = 4, + TruncateExisting = 5, + } + + [Flags] + private enum EFileAttributes : uint + { + Readonly = 0x00000001, + Hidden = 0x00000002, + System = 0x00000004, + Directory = 0x00000010, + Archive = 0x00000020, + Device = 0x00000040, + Normal = 0x00000080, + Temporary = 0x00000100, + SparseFile = 0x00000200, + ReparsePoint = 0x00000400, + Compressed = 0x00000800, + Offline = 0x00001000, + NotContentIndexed = 0x00002000, + Encrypted = 0x00004000, + Write_Through = 0x80000000, + Overlapped = 0x40000000, + NoBuffering = 0x20000000, + RandomAccess = 0x10000000, + SequentialScan = 0x08000000, + DeleteOnClose = 0x04000000, + BackupSemantics = 0x02000000, + PosixSemantics = 0x01000000, + OpenReparsePoint = 0x00200000, + OpenNoRecall = 0x00100000, + FirstPipeInstance = 0x00080000 + } + + [StructLayout (LayoutKind.Sequential)] + private struct REPARSE_DATA_BUFFER + { + /// + /// Reparse point tag. Must be a Microsoft reparse point tag. + /// + public uint ReparseTag; + + /// + /// Size, in bytes, of the data after the Reserved member. This can be calculated by: + /// (4 * sizeof(ushort)) + SubstituteNameLength + PrintNameLength + + /// (namesAreNullTerminated ? 2 * sizeof(char) : 0); + /// + public ushort ReparseDataLength; + + /// + /// Reserved; do not use. + /// + public ushort Reserved; + + /// + /// Offset, in bytes, of the substitute name string in the PathBuffer array. + /// + public ushort SubstituteNameOffset; + + /// + /// Length, in bytes, of the substitute name string. If this string is null-terminated, + /// SubstituteNameLength does not include space for the null character. + /// + public ushort SubstituteNameLength; + + /// + /// Offset, in bytes, of the print name string in the PathBuffer array. + /// + public ushort PrintNameOffset; + + /// + /// Length, in bytes, of the print name string. If this string is null-terminated, + /// PrintNameLength does not include space for the null character. + /// + public ushort PrintNameLength; + + /// + /// A buffer containing the unicode-encoded path string. The path string contains + /// the substitute name string and print name string. + /// + [MarshalAs (UnmanagedType.ByValArray, SizeConst = 0x3FF0)] + public byte[] PathBuffer; + } + + [DllImport ("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + private static extern bool DeviceIoControl (IntPtr hDevice, uint dwIoControlCode, + IntPtr InBuffer, int nInBufferSize, + IntPtr OutBuffer, int nOutBufferSize, + out int pBytesReturned, IntPtr lpOverlapped); + + [DllImport ("kernel32.dll", SetLastError = true)] + private static extern IntPtr CreateFile ( + string lpFileName, + EFileAccess dwDesiredAccess, + EFileShare dwShareMode, + IntPtr lpSecurityAttributes, + ECreationDisposition dwCreationDisposition, + EFileAttributes dwFlagsAndAttributes, + IntPtr hTemplateFile); + + /// + /// Creates a junction point from the specified directory to the specified target directory. + /// + /// + /// Only works on NTFS. + /// + /// The junction point path + /// The target directory + /// If true overwrites an existing reparse point or empty directory + /// Thrown when the junction point could not be created or when + /// an existing directory was found and if false + public static void Create (string junctionPoint, string targetDir, bool overwrite) + { + targetDir = Path.GetFullPath (targetDir); + + if (!Directory.Exists (targetDir)) + throw new IOException ("Target path does not exist or is not a directory."); + + if (Directory.Exists (junctionPoint)) { + if (!overwrite) + throw new IOException ("Directory already exists and overwrite parameter is false."); + } else { + Directory.CreateDirectory (junctionPoint); + } + + using (SafeFileHandle handle = OpenReparsePoint (junctionPoint, EFileAccess.GenericWrite)) { + byte[] targetDirBytes = Encoding.Unicode.GetBytes (NonInterpretedPathPrefix + Path.GetFullPath (targetDir)); + + REPARSE_DATA_BUFFER reparseDataBuffer = new REPARSE_DATA_BUFFER (); + + reparseDataBuffer.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; + reparseDataBuffer.ReparseDataLength = (ushort)(targetDirBytes.Length + 12); + reparseDataBuffer.SubstituteNameOffset = 0; + reparseDataBuffer.SubstituteNameLength = (ushort)targetDirBytes.Length; + reparseDataBuffer.PrintNameOffset = (ushort)(targetDirBytes.Length + 2); + reparseDataBuffer.PrintNameLength = 0; + reparseDataBuffer.PathBuffer = new byte[0x3ff0]; + Array.Copy (targetDirBytes, reparseDataBuffer.PathBuffer, targetDirBytes.Length); + + int inBufferSize = Marshal.SizeOf (reparseDataBuffer); + IntPtr inBuffer = Marshal.AllocHGlobal (inBufferSize); + + try { + Marshal.StructureToPtr (reparseDataBuffer, inBuffer, false); + + int bytesReturned; + bool result = DeviceIoControl (handle.DangerousGetHandle (), FSCTL_SET_REPARSE_POINT, + inBuffer, targetDirBytes.Length + 20, IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero); + /* + if (!result) + ThrowLastWin32Error ("Unable to create junction point."); + */ + } finally { + Marshal.FreeHGlobal (inBuffer); + } + } + } + + /// + /// Deletes a junction point at the specified source directory along with the directory itself. + /// Does nothing if the junction point does not exist. + /// + /// + /// Only works on NTFS. + /// + /// The junction point path + public static void Delete (string junctionPoint) + { + if (!Directory.Exists (junctionPoint)) { + if (File.Exists (junctionPoint)) + throw new IOException ("Path is not a junction point."); + + return; + } + + using (SafeFileHandle handle = OpenReparsePoint (junctionPoint, EFileAccess.GenericWrite)) { + REPARSE_DATA_BUFFER reparseDataBuffer = new REPARSE_DATA_BUFFER (); + + reparseDataBuffer.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; + reparseDataBuffer.ReparseDataLength = 0; + reparseDataBuffer.PathBuffer = new byte[0x3ff0]; + + int inBufferSize = Marshal.SizeOf (reparseDataBuffer); + IntPtr inBuffer = Marshal.AllocHGlobal (inBufferSize); + try { + Marshal.StructureToPtr (reparseDataBuffer, inBuffer, false); + + int bytesReturned; + bool result = DeviceIoControl (handle.DangerousGetHandle (), FSCTL_DELETE_REPARSE_POINT, + inBuffer, 8, IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero); + /* + if (!result) + ThrowLastWin32Error ("Unable to delete junction point."); + */ + } finally { + Marshal.FreeHGlobal (inBuffer); + } + + try { + Directory.Delete (junctionPoint); + } catch (IOException ex) { + throw new IOException ("Unable to delete junction point.", ex); + } + } + } + + /// + /// Determines whether the specified path exists and refers to a junction point. + /// + /// The junction point path + /// True if the specified path represents a junction point + /// Thrown if the specified path is invalid + /// or some other error occurs + public static bool Exists (string path) + { + if (!Directory.Exists (path)) + return false; + + using (SafeFileHandle handle = OpenReparsePoint (path, EFileAccess.GenericRead)) { + string target = InternalGetTarget (handle); + return target != null; + } + } + + /// + /// Gets the target of the specified junction point. + /// + /// + /// Only works on NTFS. + /// + /// The junction point path + /// The target of the junction point + /// Thrown when the specified path does not + /// exist, is invalid, is not a junction point, or some other error occurs + public static string GetTarget (string junctionPoint) + { + using (SafeFileHandle handle = OpenReparsePoint (junctionPoint, EFileAccess.GenericRead)) { + string target = InternalGetTarget (handle); + if (target == null) + throw new IOException ("Path is not a junction point."); + + return target; + } + } + + private static string InternalGetTarget (SafeFileHandle handle) + { + int outBufferSize = Marshal.SizeOf (typeof(REPARSE_DATA_BUFFER)); + IntPtr outBuffer = Marshal.AllocHGlobal (outBufferSize); + + try { + int bytesReturned; + bool result = DeviceIoControl (handle.DangerousGetHandle (), FSCTL_GET_REPARSE_POINT, + IntPtr.Zero, 0, outBuffer, outBufferSize, out bytesReturned, IntPtr.Zero); + + if (!result) { + int error = Marshal.GetLastWin32Error (); + if (error == ERROR_NOT_A_REPARSE_POINT) + return null; + /* ThrowLastWin32Error("Unable to get information about junction point."); - } + */ + } - REPARSE_DATA_BUFFER reparseDataBuffer = (REPARSE_DATA_BUFFER) - Marshal.PtrToStructure(outBuffer, typeof(REPARSE_DATA_BUFFER)); + REPARSE_DATA_BUFFER reparseDataBuffer = (REPARSE_DATA_BUFFER) + Marshal.PtrToStructure (outBuffer, typeof(REPARSE_DATA_BUFFER)); - if (reparseDataBuffer.ReparseTag != IO_REPARSE_TAG_MOUNT_POINT) - return null; + if (reparseDataBuffer.ReparseTag != IO_REPARSE_TAG_MOUNT_POINT) + return null; - string targetDir = Encoding.Unicode.GetString(reparseDataBuffer.PathBuffer, - reparseDataBuffer.SubstituteNameOffset, reparseDataBuffer.SubstituteNameLength); + string targetDir = Encoding.Unicode.GetString (reparseDataBuffer.PathBuffer, + reparseDataBuffer.SubstituteNameOffset, reparseDataBuffer.SubstituteNameLength); - if (targetDir.StartsWith(NonInterpretedPathPrefix)) - targetDir = targetDir.Substring(NonInterpretedPathPrefix.Length); + if (targetDir.StartsWith (NonInterpretedPathPrefix)) + targetDir = targetDir.Substring (NonInterpretedPathPrefix.Length); - return targetDir; - } - finally - { - Marshal.FreeHGlobal(outBuffer); - } - } + return targetDir; + } finally { + Marshal.FreeHGlobal (outBuffer); + } + } - private static SafeFileHandle OpenReparsePoint(string reparsePoint, EFileAccess accessMode) - { - SafeFileHandle reparsePointHandle = new SafeFileHandle(CreateFile(reparsePoint, accessMode, - EFileShare.Read | EFileShare.Write | EFileShare.Delete, - IntPtr.Zero, ECreationDisposition.OpenExisting, - EFileAttributes.BackupSemantics | EFileAttributes.OpenReparsePoint, IntPtr.Zero), true); + private static SafeFileHandle OpenReparsePoint (string reparsePoint, EFileAccess accessMode) + { + SafeFileHandle reparsePointHandle = new SafeFileHandle (CreateFile (reparsePoint, accessMode, + EFileShare.Read | EFileShare.Write | EFileShare.Delete, + IntPtr.Zero, ECreationDisposition.OpenExisting, + EFileAttributes.BackupSemantics | EFileAttributes.OpenReparsePoint, IntPtr.Zero), true); + /* if (Marshal.GetLastWin32Error() != 0) ThrowLastWin32Error("Unable to open reparse point."); - - return reparsePointHandle; - } - + */ + return reparsePointHandle; + } + /* private static void ThrowLastWin32Error(string message) { throw new IOException(message, Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error())); } - } + */ + } } diff --git a/UnityPlugin/Projeny/Main/PrjInterface.cs b/UnityPlugin/Projeny/Main/PrjInterface.cs index dcd757e9..2e9bb0c7 100644 --- a/UnityPlugin/Projeny/Main/PrjInterface.cs +++ b/UnityPlugin/Projeny/Main/PrjInterface.cs @@ -15,318 +15,307 @@ namespace Projeny { - public class PrjResponse - { - public readonly bool Succeeded; - public readonly string ErrorMessage; - public readonly string Output; - - PrjResponse( - bool succeeded, string errorMessage, string output) - { - Succeeded = succeeded; - ErrorMessage = errorMessage; - Output = output; - } - - public static PrjResponse Error(string errorMessage) - { - return new PrjResponse(false, errorMessage, null); - } - - public static PrjResponse Success(string output = null) - { - return new PrjResponse(true, null, output); - } - } - - public class PrjRequest - { - public string RequestId; - public string ProjectName; - public BuildTarget Platform; - public string ConfigPath; - public string Param1; - public string Param2; - public string Param3; - } - - public static class PrjInterface - { - static string _configPath; - static string _prjApiPath; - - public static string ConfigPath - { - get - { - if (_configPath == null) - { - _configPath = SearchForConfigPath(); - Assert.IsNotNull(_configPath); - } - - return _configPath; - } - } - - static string PrjEditorApiPath - { - get - { - if (_prjApiPath == null) - { - _prjApiPath = FindPrjExePath(); - Assert.IsNotNull(_prjApiPath); - } - - return _prjApiPath; - } - } - - static string FindPrjExePath() - { - var settingPrefix = "EditorApiRelativePath:"; - - // First check for for a path in the config file - foreach (var line in File.ReadAllLines(ConfigPath)) - { - if (line.StartsWith(settingPrefix)) - { - var relativePath = line.Substring(settingPrefix.Length + 1).Trim(); - var fullPath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(ConfigPath), relativePath)); - Assert.That(File.Exists(fullPath)); - return fullPath; - } - } - - try - { - return PathUtil.FindExePathFromEnvPath("PrjEditorApi.bat"); - } - catch (FileNotFoundException) - { - throw new PrjException( - "Could not locate path to PRJ.bat. Have you added 'projeny/Bin/Prj' to your environment PATH? See documentation for details."); - } - } - - static string SearchForConfigPath() - { - foreach (var dirInfo in PathUtil.GetAllParentDirectories(Application.dataPath)) - { - var configPath = Path.Combine(dirInfo.FullName, ProjenyEditorUtil.ConfigFileName); - - if (File.Exists(configPath)) - { - return configPath; - } - } - - throw new PrjException( - "Could not locate {0} when searching from {1} upwards".Fmt(ProjenyEditorUtil.ConfigFileName, Application.dataPath)); - } - - public static PrjRequest CreatePrjRequest(string requestId) - { - return CreatePrjRequestForProjectAndPlatform( - requestId, - ProjenyEditorUtil.GetCurrentProjectName(), - ProjenyEditorUtil.GetPlatformFromDirectoryName()); - } - - public static PrjRequest CreatePrjRequestForProject( - string requestId, string project) - { - return CreatePrjRequestForProjectAndPlatform( - requestId, - project, - ProjenyEditorUtil.GetPlatformFromDirectoryName()); - } - - public static PrjRequest CreatePrjRequestForPlatform( - string requestId, BuildTarget platform) - { - return CreatePrjRequestForProjectAndPlatform( - requestId, - ProjenyEditorUtil.GetCurrentProjectName(), - platform); - } - - public static PrjRequest CreatePrjRequestForProjectAndPlatform( - string requestId, string projectName, BuildTarget platform) - { - return new PrjRequest() - { - RequestId = requestId, - ProjectName = projectName, - Platform = platform, - ConfigPath = ConfigPath - }; - } - - static ProcessStartInfo GetPrjProcessStartInfo(PrjRequest request) - { - var startInfo = new ProcessStartInfo(); - - startInfo.FileName = PrjEditorApiPath; - - var argStr = "\"{0}\" \"{1}\" {2} {3}" - .Fmt( - request.ConfigPath, request.ProjectName, - ToPlatformDirStr(request.Platform), request.RequestId); - - if (request.Param1 != null) - { - argStr += " \"{0}\"".Fmt(request.Param1); - } - - if (request.Param2 != null) - { - argStr += " \"{0}\"".Fmt(request.Param2); - } - - if (request.Param3 != null) - { - argStr += " \"{0}\"".Fmt(request.Param3); - } - - startInfo.Arguments = argStr; - startInfo.CreateNoWindow = true; - startInfo.UseShellExecute = false; - startInfo.RedirectStandardOutput = true; - startInfo.RedirectStandardError = true; - - Log.Debug("Running command '{0} {1}'".Fmt(startInfo.FileName, startInfo.Arguments)); - - return startInfo; - } - - public static PrjResponse RunPrj(PrjRequest request) - { - Process proc = new Process(); - proc.StartInfo = GetPrjProcessStartInfo(request); - - proc.Start(); - - var errorLines = new List(); - proc.ErrorDataReceived += (sender, outputArgs) => errorLines.Add(outputArgs.Data); - - var outputLines = new List(); - proc.OutputDataReceived += (sender, outputArgs) => outputLines.Add(outputArgs.Data); - - proc.BeginErrorReadLine(); - proc.BeginOutputReadLine(); - - proc.WaitForExit(); - - return RunPrjCommonEnd( - proc, errorLines.Join(Environment.NewLine)); - } - - // This will yield string values that contain some status message - // until finally yielding a value of type PrjResponse with the final data - public static IEnumerator RunPrjAsync(PrjRequest request) - { - Process proc = new Process(); - proc.StartInfo = GetPrjProcessStartInfo(request); - - proc.EnableRaisingEvents = true; - - bool hasExited = false; - proc.Exited += delegate - { - hasExited = true; - }; - - proc.Start(); - - var errorLines = new List(); - proc.ErrorDataReceived += (sender, outputArgs) => errorLines.Add(outputArgs.Data); - - var outputLines = new List(); - proc.OutputDataReceived += (sender, outputArgs) => outputLines.Add(outputArgs.Data); - - proc.BeginErrorReadLine(); - proc.BeginOutputReadLine(); - - while (!hasExited) - { - if (outputLines.IsEmpty()) - { - yield return null; - } - else - { - var newLines = outputLines.ToList(); - outputLines.Clear(); - yield return newLines; - } - } - - yield return RunPrjCommonEnd( - proc, errorLines.Join(Environment.NewLine)); - } - - static PrjResponse RunPrjCommonEnd( - Process proc, string errorOutput) - { - // If it returns an error code, then assume that - // the contents of STDERR are the error message to display - // to the user - // Otherwise, assume the contents of STDERR are the final output - // data. This can include things like serialized YAML - if (proc.ExitCode != 0) - { - return PrjResponse.Error(errorOutput); - } - - return PrjResponse.Success(errorOutput); - } - - static string ToPlatformDirStr(BuildTarget platform) - { - switch (platform) - { - case BuildTarget.StandaloneWindows: - { - return "windows"; - } - case BuildTarget.Android: - { - return "android"; - } - case BuildTarget.WebPlayer: - { - return "webplayer"; - } - case BuildTarget.WebGL: - { - return "webgl"; - } - case BuildTarget.StandaloneOSXUniversal: - { - return "osx"; - } - case BuildTarget.iOS: - { - return "ios"; - } - case BuildTarget.StandaloneLinux: - { - return "linux"; - } - } - - throw new NotImplementedException(); - } - - public class PrjException : Exception - { - public PrjException(string errorMessage) - : base(errorMessage) - { - } - } - } + public class PrjResponse + { + public readonly bool Succeeded; + public readonly string ErrorMessage; + public readonly string Output; + + PrjResponse ( + bool succeeded, string errorMessage, string output) + { + Succeeded = succeeded; + ErrorMessage = errorMessage; + Output = output; + } + + public static PrjResponse Error (string errorMessage) + { + return new PrjResponse (false, errorMessage, null); + } + + public static PrjResponse Success (string output = null) + { + return new PrjResponse (true, null, output); + } + } + + public class PrjRequest + { + public string RequestId; + public string ProjectName; + public BuildTarget Platform; + public string ConfigPath; + public string Param1; + public string Param2; + public string Param3; + } + + public static class PrjInterface + { + static string _configPath; + static string _prjApiPath; + + public static string ConfigPath { + get { + if (_configPath == null) { + _configPath = SearchForConfigPath (); + Assert.IsNotNull (_configPath); + } + + return _configPath; + } + } + + static string PrjEditorApiPath { + get { + if (_prjApiPath == null) { + _prjApiPath = FindPrjExePath (); + Assert.IsNotNull (_prjApiPath); + } + + return _prjApiPath; + } + } + + static string FindPrjExePath () + { + var settingPrefix = "EditorApiRelativePath:"; + + UnityEngine.Debug.Log (ConfigPath); + + // First check for for a path in the config file + foreach (var line in File.ReadAllLines(ConfigPath)) { + if (line.StartsWith (settingPrefix)) { + var relativePath = line.Substring (settingPrefix.Length + 1).Trim (); + var fullPath = Path.GetFullPath (Path.Combine (Path.GetDirectoryName (ConfigPath), relativePath)); + Assert.That (File.Exists (fullPath), "Could not locate path to prj. Have you added 'projeny/source/bin/prj' to your environment PATH? See documentation for details."); + return fullPath; + } + } + throw new PrjException ("Could not locate path to prj. Have you added 'projeny/source/bin/prj' to your environment PATH? See documentation for details."); + } + + static string SearchForConfigPath () + { + foreach (var dirInfo in PathUtil.GetAllParentDirectories(Application.dataPath)) { + var configPath = Path.Combine (dirInfo.FullName, ProjenyEditorUtil.ConfigFileName); + + if (File.Exists (configPath)) { + return configPath; + } + } + + throw new PrjException ( + "Could not locate {0} when searching from {1} upwards".Fmt (ProjenyEditorUtil.ConfigFileName, Application.dataPath)); + } + + public static PrjRequest CreatePrjRequest (string requestId) + { + return CreatePrjRequestForProjectAndPlatform ( + requestId, + ProjenyEditorUtil.GetCurrentProjectName (), + ProjenyEditorUtil.GetPlatformFromDirectoryName ()); + } + + public static PrjRequest CreatePrjRequestForProject ( + string requestId, string project) + { + return CreatePrjRequestForProjectAndPlatform ( + requestId, + project, + ProjenyEditorUtil.GetPlatformFromDirectoryName ()); + } + + public static PrjRequest CreatePrjRequestForPlatform ( + string requestId, BuildTarget platform) + { + return CreatePrjRequestForProjectAndPlatform ( + requestId, + ProjenyEditorUtil.GetCurrentProjectName (), + platform); + } + + public static PrjRequest CreatePrjRequestForProjectAndPlatform ( + string requestId, string projectName, BuildTarget platform) + { +// switch (requestId.ToLower ()) { +// case "updatelinks": +// requestId = "-ul"; +// break; +// case "updatecustomsolution": +// requestId = "-ucs"; +// break; +// case "opencustomsolution": +// requestId = "-ocs"; +// break; +// case "listpackages": +// requestId = "-lpa"; +// break; +// case "" +// } + + requestId = "--" + requestId; + return new PrjRequest () { + RequestId = requestId, + ProjectName = projectName, + Platform = platform, + ConfigPath = ConfigPath + }; + } + + static ProcessStartInfo GetPrjProcessStartInfo (PrjRequest request) + { + var startInfo = new ProcessStartInfo (); + + startInfo.FileName = PrjEditorApiPath; + + var argStr = "-cfg \"{0}\" -p \"{1}\" -pl {2} {3}" + .Fmt ( + request.ConfigPath, request.ProjectName, + ToPlatformDirStr (request.Platform), request.RequestId); + + if (request.Param1 != null) { + argStr += " \"{0}\"".Fmt (request.Param1); + } + + if (request.Param2 != null) { + argStr += " \"{0}\"".Fmt (request.Param2); + } + + if (request.Param3 != null) { + argStr += " \"{0}\"".Fmt (request.Param3); + } + + startInfo.Arguments = argStr; + startInfo.CreateNoWindow = true; + startInfo.UseShellExecute = false; + startInfo.RedirectStandardOutput = true; + startInfo.RedirectStandardError = true; + + Log.Debug ("Running command '{0} {1}'".Fmt (startInfo.FileName, startInfo.Arguments)); + + return startInfo; + } + + public static PrjResponse RunPrj (PrjRequest request) + { + Process proc = new Process (); + proc.StartInfo = GetPrjProcessStartInfo (request); + + proc.Start (); + + var errorLines = new List (); + proc.ErrorDataReceived += (sender, outputArgs) => errorLines.Add (outputArgs.Data); + + var outputLines = new List (); + proc.OutputDataReceived += (sender, outputArgs) => outputLines.Add (outputArgs.Data); + + proc.BeginErrorReadLine (); + proc.BeginOutputReadLine (); + + proc.WaitForExit (); + + return RunPrjCommonEnd ( + proc, errorLines.Join (Environment.NewLine)); + } + + // This will yield string values that contain some status message + // until finally yielding a value of type PrjResponse with the final data + public static IEnumerator RunPrjAsync (PrjRequest request) + { + Process proc = new Process (); + proc.StartInfo = GetPrjProcessStartInfo (request); + + proc.EnableRaisingEvents = true; + + bool hasExited = false; + proc.Exited += delegate { + hasExited = true; + }; + + proc.Start (); + + var errorLines = new List (); + proc.ErrorDataReceived += (sender, outputArgs) => errorLines.Add (outputArgs.Data); + + var outputLines = new List (); + proc.OutputDataReceived += (sender, outputArgs) => outputLines.Add (outputArgs.Data); + + proc.BeginErrorReadLine (); + proc.BeginOutputReadLine (); + + while (!hasExited) { + if (outputLines.IsEmpty ()) { + yield return null; + } else { + var newLines = outputLines.ToList (); + outputLines.Clear (); + yield return newLines; + } + } + + yield return RunPrjCommonEnd ( + proc, errorLines.Join (Environment.NewLine)); + } + + static PrjResponse RunPrjCommonEnd ( + Process proc, string errorOutput) + { + // If it returns an error code, then assume that + // the contents of STDERR are the error message to display + // to the user + // Otherwise, assume the contents of STDERR are the final output + // data. This can include things like serialized YAML + if (proc.ExitCode != 0) { + return PrjResponse.Error (errorOutput); + } + + return PrjResponse.Success (errorOutput); + } + + static string ToPlatformDirStr (BuildTarget platform) + { + switch (platform) { + case BuildTarget.StandaloneWindows: + { + return "win"; + } + case BuildTarget.Android: + { + return "and"; + } + case BuildTarget.WebPlayer: + { + return "webp"; + } + case BuildTarget.WebGL: + { + return "webgl"; + } + case BuildTarget.StandaloneOSXUniversal: + { + return "osx"; + } + case BuildTarget.iOS: + { + return "ios"; + } + case BuildTarget.StandaloneLinux: + { + return "lin"; + } + } + + throw new NotImplementedException (); + } + + public class PrjException : Exception + { + public PrjException (string errorMessage) + : base (errorMessage) + { + } + } + } } diff --git a/UnityPlugin/Projeny/Main/PrjLocationGetter.cs b/UnityPlugin/Projeny/Main/PrjLocationGetter.cs new file mode 100644 index 00000000..54ace062 --- /dev/null +++ b/UnityPlugin/Projeny/Main/PrjLocationGetter.cs @@ -0,0 +1,30 @@ +using System; +using System.Diagnostics; +using UnityEngine; + +namespace Projeny +{ + public class PrjLocationGetter + { + public static string GetPrjPath () + { + Process p = new Process (); + p.StartInfo.FileName = "which"; + p.StartInfo.Arguments = "prj"; + p.StartInfo.UseShellExecute = false; + p.StartInfo.RedirectStandardOutput = true; + p.StartInfo.RedirectStandardError = true; + p.Start (); + + string output = p.StandardOutput.ReadToEnd (); + string error = p.StandardError.ReadToEnd (); + p.WaitForExit (); + + UnityEngine.Debug.Log ("output: " + output); + UnityEngine.Debug.Log ("error: " + error); + + return output; + } + } +} + diff --git a/UnityPlugin/Projeny/PackageManager/Model/PmModel.cs b/UnityPlugin/Projeny/PackageManager/Model/PmModel.cs index 59421941..edf2d386 100644 --- a/UnityPlugin/Projeny/PackageManager/Model/PmModel.cs +++ b/UnityPlugin/Projeny/PackageManager/Model/PmModel.cs @@ -10,291 +10,267 @@ namespace Projeny.Internal { - public enum PmViewStates - { - ReleasesAndPackages, - PackagesAndProject, - Project, - ProjectAndVisualStudio, - } - - [Serializable] - public class PmModel - { - public event Action PluginItemsChanged = delegate {}; - public event Action AssetItemsChanged = delegate {}; - public event Action PackageFoldersChanged = delegate {}; - public event Action ReleasesChanged = delegate {}; - public event Action VsProjectsChanged = delegate {}; - public event Action PackageFolderIndexChanged = delegate {}; - - [SerializeField] - List _folderInfos = new List(); - - [SerializeField] - List _releases = new List(); - - [SerializeField] - List _assetItems = new List(); - - [SerializeField] - List _pluginItems = new List(); - - [SerializeField] - List _vsProjects = new List(); - - [SerializeField] - List _prebuilt = new List(); - - [SerializeField] - Dictionary _solutionFolders = new Dictionary(); - - [SerializeField] - string _projectSettingsPath; - - [SerializeField] - int _packageFolderIndex; - - public PmModel() - { - } - - public int PackageFolderIndex - { - get - { - return _packageFolderIndex; - } - set - { - if (_packageFolderIndex != value) - { - _packageFolderIndex = value; - PackageFolderIndexChanged(); - } - } - } - - public string ProjectSettingsPath - { - get - { - return _projectSettingsPath; - } - set - { - _projectSettingsPath = value; - } - } - - public IEnumerable Releases - { - get - { - return _releases; - } - } - - public IEnumerable AssetItems - { - get - { - return _assetItems; - } - } - - public IEnumerable AllPackages - { - get - { - return _folderInfos.SelectMany(x => x.Packages); - } - } - - public IEnumerable PluginItems - { - get - { - return _pluginItems; - } - } - - public IEnumerable PackageFolders - { - get - { - return _folderInfos; - } - } - - public IEnumerable PrebuiltProjects - { - get - { - return _prebuilt; - } - } - - public IEnumerable VsProjects - { - get - { - return _vsProjects; - } - } - - public IEnumerable> VsSolutionFolders - { - get - { - return _solutionFolders; - } - } - - public string GetCurrentPackageFolderPath() - { - var folderPath = TryGetCurrentPackageFolderPath(); - Assert.IsNotNull(folderPath, "Could not find current package root folder path"); - return folderPath; - } - - public string TryGetCurrentPackageFolderPath() - { - if (_packageFolderIndex >= 0 && _packageFolderIndex < _folderInfos.Count) - { - return _folderInfos[_packageFolderIndex].Path; - } - - return null; - } - - public IEnumerable GetCurrentFolderPackages() - { - if (_packageFolderIndex >= 0 && _packageFolderIndex < _folderInfos.Count) - { - return _folderInfos[_packageFolderIndex].Packages; - } - - return Enumerable.Empty(); - } - - public void ClearSolutionFolders() - { - _solutionFolders.Clear(); - } - - public void ClearPrebuiltProjects() - { - _prebuilt.Clear(); - } - - public void ClearSolutionProjects() - { - _vsProjects.Clear(); - VsProjectsChanged(); - } - - public void ClearAssetItems() - { - _assetItems.Clear(); - AssetItemsChanged(); - } - - public void RemoveVsProject(string name) - { - _vsProjects.RemoveWithConfirm(name); - VsProjectsChanged(); - } - - public void RemoveAssetItem(string name) - { - _assetItems.RemoveWithConfirm(name); - AssetItemsChanged(); - } - - public bool HasAssetItem(string name) - { - return _assetItems.Contains(name); - } - - public bool HasVsProject(string name) - { - return _vsProjects.Contains(name); - } - - public bool HasPluginItem(string name) - { - return _pluginItems.Contains(name); - } - - public void RemovePluginItem(string name) - { - _pluginItems.RemoveWithConfirm(name); - PluginItemsChanged(); - } - - public void AddVsProject(string name) - { - _vsProjects.Add(name); - VsProjectsChanged(); - } - - public void AddPrebuilt(string value) - { - _prebuilt.Add(value); - } - - public void AddSolutionFolder(string key, string value) - { - _solutionFolders.Add(key, value); - } - - public void AddAssetItem(string name) - { - _assetItems.Add(name); - AssetItemsChanged(); - } - - public void AddPluginItem(string name) - { - _pluginItems.Add(name); - PluginItemsChanged(); - } - - public void ClearPluginItems() - { - _pluginItems.Clear(); - PluginItemsChanged(); - } - - public void SetPackageFolders(List folderInfos) - { - _folderInfos.Clear(); - _folderInfos.AddRange(folderInfos); - PackageFoldersChanged(); - } - - public void SetReleases(List releases) - { - Assert.That(releases.All(x => !string.IsNullOrEmpty(x.Name))); - _releases.Clear(); - _releases.AddRange(releases); - ReleasesChanged(); - } - - public bool IsPackageAddedToProject(string name) - { - return _assetItems.Concat(_pluginItems).Contains(name); - } - - public bool IsReleaseInstalled(ReleaseInfo info) - { - return AllPackages - .Any(x => x.InstallInfo != null - && x.InstallInfo.ReleaseInfo != null - && x.InstallInfo.ReleaseInfo.Id == info.Id - && x.InstallInfo.ReleaseInfo.VersionCode == info.VersionCode); - } - } + public enum PmViewStates + { + ReleasesAndPackages, + PackagesAndProject, + Project, + ProjectAndVisualStudio, + } + + [Serializable] + public class PmModel + { + public event Action PluginItemsChanged = delegate {}; + public event Action AssetItemsChanged = delegate {}; + public event Action PackageFoldersChanged = delegate {}; + public event Action ReleasesChanged = delegate {}; + public event Action VsProjectsChanged = delegate {}; + public event Action PackageFolderIndexChanged = delegate {}; + + [SerializeField] + List _folderInfos = new List (); + + [SerializeField] + List _releases = new List (); + + [SerializeField] + List _assetItems = new List (); + + [SerializeField] + List _pluginItems = new List (); + + [SerializeField] + List _vsProjects = new List (); + + [SerializeField] + List _prebuilt = new List (); + + [SerializeField] + Dictionary _solutionFolders = new Dictionary (); + + [SerializeField] + string _projectSettingsPath; + + [SerializeField] + int _packageFolderIndex; + + public PmModel () + { + } + + public int PackageFolderIndex { + get { + return _packageFolderIndex; + } + set { + if (_packageFolderIndex != value) { + _packageFolderIndex = value; + PackageFolderIndexChanged (); + } + } + } + + public string ProjectSettingsPath { + get { + return _projectSettingsPath; + } + set { + _projectSettingsPath = value; + } + } + + public IEnumerable Releases { + get { + return _releases; + } + } + + public IEnumerable AssetItems { + get { + return _assetItems; + } + } + + public IEnumerable AllPackages { + get { + return _folderInfos.SelectMany (x => x.Packages); + } + } + + public IEnumerable PluginItems { + get { + return _pluginItems; + } + } + + public IEnumerable PackageFolders { + get { + return _folderInfos; + } + } + + public IEnumerable PrebuiltProjects { + get { + return _prebuilt; + } + } + + public IEnumerable VsProjects { + get { + return _vsProjects; + } + } + + public IEnumerable> VsSolutionFolders { + get { + return _solutionFolders; + } + } + + public string GetCurrentPackageFolderPath () + { + var folderPath = TryGetCurrentPackageFolderPath (); + Assert.IsNotNull (folderPath, "Could not find current package root folder path"); + return folderPath; + } + + public string TryGetCurrentPackageFolderPath () + { + UnityEngine.Debug.Log (_folderInfos [_packageFolderIndex].Path); + if (_packageFolderIndex >= 0 && _packageFolderIndex < _folderInfos.Count) { + return _folderInfos [_packageFolderIndex].Path; + } + + return null; + } + + public IEnumerable GetCurrentFolderPackages () + { + if (_packageFolderIndex >= 0 && _packageFolderIndex < _folderInfos.Count) { + return _folderInfos [_packageFolderIndex].Packages; + } + + return Enumerable.Empty (); + } + + public void ClearSolutionFolders () + { + _solutionFolders.Clear (); + } + + public void ClearPrebuiltProjects () + { + _prebuilt.Clear (); + } + + public void ClearSolutionProjects () + { + _vsProjects.Clear (); + VsProjectsChanged (); + } + + public void ClearAssetItems () + { + _assetItems.Clear (); + AssetItemsChanged (); + } + + public void RemoveVsProject (string name) + { + _vsProjects.RemoveWithConfirm (name); + VsProjectsChanged (); + } + + public void RemoveAssetItem (string name) + { + _assetItems.RemoveWithConfirm (name); + AssetItemsChanged (); + } + + public bool HasAssetItem (string name) + { + return _assetItems.Contains (name); + } + + public bool HasVsProject (string name) + { + return _vsProjects.Contains (name); + } + + public bool HasPluginItem (string name) + { + return _pluginItems.Contains (name); + } + + public void RemovePluginItem (string name) + { + _pluginItems.RemoveWithConfirm (name); + PluginItemsChanged (); + } + + public void AddVsProject (string name) + { + _vsProjects.Add (name); + VsProjectsChanged (); + } + + public void AddPrebuilt (string value) + { + _prebuilt.Add (value); + } + + public void AddSolutionFolder (string key, string value) + { + _solutionFolders.Add (key, value); + } + + public void AddAssetItem (string name) + { + _assetItems.Add (name); + AssetItemsChanged (); + } + + public void AddPluginItem (string name) + { + _pluginItems.Add (name); + PluginItemsChanged (); + } + + public void ClearPluginItems () + { + _pluginItems.Clear (); + PluginItemsChanged (); + } + + public void SetPackageFolders (List folderInfos) + { + _folderInfos.Clear (); + _folderInfos.AddRange (folderInfos); + PackageFoldersChanged (); + } + + public void SetReleases (List releases) + { + Assert.That (releases.All (x => !string.IsNullOrEmpty (x.Name))); + _releases.Clear (); + _releases.AddRange (releases); + ReleasesChanged (); + } + + public bool IsPackageAddedToProject (string name) + { + return _assetItems.Concat (_pluginItems).Contains (name); + } + + public bool IsReleaseInstalled (ReleaseInfo info) + { + return AllPackages + .Any (x => x.InstallInfo != null + && x.InstallInfo.ReleaseInfo != null + && x.InstallInfo.ReleaseInfo.Id == info.Id + && x.InstallInfo.ReleaseInfo.VersionCode == info.VersionCode); + } + } } diff --git a/UnityPlugin/Projeny/Projeny.csproj b/UnityPlugin/Projeny/Projeny.csproj index 7d6d2b86..6c940221 100644 --- a/UnityPlugin/Projeny/Projeny.csproj +++ b/UnityPlugin/Projeny/Projeny.csproj @@ -2,13 +2,11 @@ - Debug + Unity Debug AnyCPU {A8FDE485-31E0-4B40-8E7B-476E82374944} Library Properties - - $(MSBuildProjectName) v3.5 512 @@ -117,15 +115,16 @@ + - {463bdb34-890f-4375-a79f-37950f5457dc} + {463BDB34-890F-4375-A79F-37950F5457DC} YamlDotNet - +