Пример #1
0
class LocalFolderReleaseSource:
    _log = Inject('Logger')
    _sys = Inject('SystemHelper')
    _extractor = Inject('UnityPackageExtractor')
    _packageAnalyzer = Inject('UnityPackageAnalyzer')

    def __init__(self, folderPath):
        self._folderPath = folderPath
        self._files = []

    @property
    def releases(self):
        return [x.release for x in self._files]

    def init(self):
        with self._log.heading('Initializing release source for local folder'):
            self._log.debug('Initializing release source for local folder "{0}"', self._folderPath)
            for path in self._sys.findFilesByPattern(self._folderPath, '*.unitypackage'):
                release = self._packageAnalyzer.getReleaseInfoFromUnityPackage(path)

                self._files.append(FileInfo(path, release))

            self._log.info("Found {0} released in folder '{1}'", len(self._files), self._folderPath)

    def getName(self):
        return "Local Folder ({0})".format(self._folderPath)

    # Should return the chosen name for the package
    # If forcedName is non-null then this should always be the value of forcedName
    def installRelease(self, packageRootDir, releaseInfo, forcedName):
        fileInfo = next(x for x in self._files if x.release == releaseInfo)
        assertIsNotNone(fileInfo)

        return self._extractor.extractUnityPackage(packageRootDir, fileInfo.path, releaseInfo.name, forcedName)
Пример #2
0
class JunctionHelper:
    """
    Misc. helper functions related to windows junctions
    """
    _varMgr = Inject('VarManager')
    _log = Inject('Logger')
    _sys = Inject('SystemHelper')

    def __init__(self):
        pass

    def removeJunction(self, linkDir):
        linkDir = self._varMgr.expand(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))
            except Exception as e:
                raise Exception('Failed while attempting to delete junction "{0}":\n{1}'.format(linkDir, str(e))) from e

            return True

        return False

    def makeJunction(self, actualPath, linkPath):
        actualPath = self._varMgr.expandPath(actualPath)
        linkPath = self._varMgr.expandPath(linkPath)

        assertThat(self._sys.directoryExists(actualPath))

        self._sys.makeMissingDirectoriesInPath(linkPath)

        self._log.debug('Making junction 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))

    def removeJunctionsInDirectory(self, dirPath, recursive):
        fullDirPath = self._varMgr.expandPath(dirPath)

        if not os.path.exists(fullDirPath):
            return

        for name in os.listdir(fullDirPath):
            fullPath = os.path.join(fullDirPath, name)

            if not os.path.isdir(fullPath):
                continue

            if self.removeJunction(fullPath):
                if os.path.exists(fullPath + '.meta'):
                    os.remove(fullPath + '.meta')

                self._log.debug('Removed directory for package "{0}"'.format(name))
            else:
                if recursive:
                    self.removeJunctionsInDirectory(fullPath, True)
Пример #3
0
class RemoteServerReleaseSource:
    _log = Inject('Logger')
    _sys = Inject('SystemHelper')
    _packageExtractor = Inject('UnityPackageExtractor')

    def __init__(self, manifestUrl):
        self._manifestUrl = manifestUrl
        self._releaseInfos = []

    @property
    def releases(self):
        return self._releaseInfos

    def init(self):
        with self._log.heading("Initializing remote server release source"):
            self._log.debug(
                "Initializing remote server release source with URL '{0}'",
                self._manifestUrl)
            response = urllib.request.urlopen(self._manifestUrl)
            manifestData = response.read().decode('utf-8')

            self._log.debug(
                "Got manifest with data: \n{0}".format(manifestData))

            self._manifest = YamlSerializer.deserialize(manifestData)

            for info in self._manifest.releases:
                info.url = urllib.parse.urljoin(self._manifestUrl,
                                                info.localPath)
                info.localPath = None
                self._releaseInfos.append(info)

    def getName(self):
        return "File Server"

    def installRelease(self, packageRootDir, releaseInfo, forcedName):
        assertThat(releaseInfo.url)

        try:
            with self._log.heading("Downloading release from url '{0}'".format(
                    releaseInfo.url)):
                with tempfile.NamedTemporaryFile(
                        delete=False, suffix='.unitypackage') as tempFile:
                    tempFilePath = tempFile.name

                self._log.debug(
                    "Downloading url to temporary file '{0}'".format(
                        tempFilePath))
                urllib.request.urlretrieve(releaseInfo.url, tempFilePath)

                return self._packageExtractor.extractUnityPackage(
                    packageRootDir, tempFilePath, releaseInfo.name, forcedName)
        finally:
            if os.path.exists(tempFilePath):
                os.remove(tempFilePath)
Пример #4
0
class ProjectConfigChanger:
    _log = Inject('Logger')
    _sys = Inject('SystemHelper')
    _packageManager = Inject('PackageManager')
    _varMgr = Inject('VarManager')

    def _getProjectConfigPath(self, projectName):
        return self._varMgr.expandPath('[UnityProjectsDir]/{0}/{1}'.format(
            projectName, ProjectConfigFileName))

    def _loadProjectConfig(self, projectName):
        configPath = self._getProjectConfigPath(projectName)

        yamlData = YamlSerializer.deserialize(
            self._sys.readFileAsText(configPath))

        result = ProjectConfig()

        for pair in yamlData.__dict__.items():
            result.__dict__[pair[0]] = pair[1]

        return result

    def _saveProjectConfig(self, projectName, projectConfig):
        configPath = self._getProjectConfigPath(projectName)
        self._sys.writeFileAsText(configPath,
                                  YamlSerializer.serialize(projectConfig))

    def addPackage(self, projectName, packageName, addToAssetsFolder):
        with self._log.heading('Adding package {0} to project {1}'.format(
                packageName, projectName)):
            assertThat(
                packageName in self._packageManager.getAllPackageNames(),
                "Could not find the given package '{0}' in the UnityPackages folder",
                packageName)
            self._packageManager.setPathsForProjectPlatform(
                projectName, Platforms.Windows)

            projConfig = self._loadProjectConfig(projectName)

            assertThat(
                packageName not in projConfig.assetsFolder
                and packageName not in projConfig.pluginsFolder,
                "Given package '{0}' has already been added to project config",
                packageName)

            if addToAssetsFolder:
                projConfig.assetsFolder.append(packageName)
            else:
                projConfig.pluginsFolder.append(packageName)

            self._saveProjectConfig(projectName, projConfig)

            self._log.good("Added package '{0}' to file '{1}/{2}'",
                           packageName, projectName, ProjectConfigFileName)
Пример #5
0
class Runner:
    _log = Inject('Logger')
    _sys = Inject('SystemHelper')

    def run(self, args):
        self._args = args

        for filePath in self._sys.findFilesByPattern(self._args.directory,
                                                     '*.py'):
            self._sys.executeAndWait(
                'autoflake --in-place --remove-unused-variables "{0}"'.format(
                    filePath))
Пример #6
0
class ScriptRunner:
    _log = Inject('Logger')

    def runWrapper(self, runner):
        startTime = datetime.now()

        succeeded = False

        try:
            runner()
            succeeded = True

        except KeyboardInterrupt as e:
            self._log.error('Operation aborted by user by hitting CTRL+C')

        except Exception as e:
            self._log.error(str(e))

            # Only print stack trace if it's a build-script error
            if not isinstance(e, ProcessErrorCodeException) and not isinstance(e, ProcessTimeoutException):
                if MiscUtil.isRunningAsExe():
                    self._log.noise('\n' + traceback.format_exc())
                else:
                    self._log.debug('\n' + traceback.format_exc())

        totalSeconds = (datetime.now()-startTime).total_seconds()
        totalSecondsStr = Util.formatTimeDelta(totalSeconds)

        if succeeded:
            self._log.good('Operation completed successfully.  Took ' + totalSecondsStr + '.\n')
        else:
            self._log.info('Operation completed with errors.  Took ' + totalSecondsStr + '.\n')

        return succeeded
Пример #7
0
class Test2:
    qux = Inject('Qux')

    def __init__(self):
        self.X = 0

    def Run(self):
        print(self.qux.GetValue())
Пример #8
0
class Foo2:
    foo1 = Inject('Foo1')

    def __init__(self, val):
        self.val = val

    def Start(self):
        print('foo2 {0}, foo1 {1}'.format(self.val, self.foo1.val))
Пример #9
0
class Foo1:
    foo2 = Inject('Foo2')

    def __init__(self, val):
        self.val = val

    def Start(self):
        print(self.foo2.val)
Пример #10
0
class CommonSettings:
    _config = Inject('Config')

    def __init__(self):
        self.maxProjectNameLength = self._config.getInt('MaxProjectNameLength')

    def getShortProjectName(self, val):
        return val[0:self.maxProjectNameLength]
Пример #11
0
class ZipHelper:
    _sys = Inject('SystemHelper')
    _varMgr = Inject('VarManager')
    _log = Inject('Logger')

    def createZipFile(self, dirPath, zipFilePath):
        assertThat(zipFilePath.endswith('.zip'))

        dirPath = self._varMgr.expandPath(dirPath)
        zipFilePath = self._varMgr.expandPath(zipFilePath)

        self._sys.makeMissingDirectoriesInPath(zipFilePath)
        self._sys.removeFileIfExists(zipFilePath)

        self._log.debug("Writing directory '{0}' to zip at '{1}'", dirPath,
                        zipFilePath)
        self._writeDirectoryToZipFile(zipFilePath, dirPath)

    def _writeDirectoryToZipFile(self, zipFilePath, dirPath):
        with zipfile.ZipFile(zipFilePath, 'w', zipfile.ZIP_DEFLATED) as zipf:
            self._zipAddDir(zipf, dirPath, '')

    def _zipAddDir(self, zipf, dirPath, zipPathPrefix=None):
        dirPath = self._varMgr.expandPath(dirPath)

        assertThat(os.path.isdir(dirPath),
                   'Invalid directory given at "{0}"'.format(dirPath))

        if zipPathPrefix is None:
            zipPathPrefix = os.path.basename(dirPath)

        for root, dirs, files in os.walk(dirPath):
            for file in files:
                filePath = os.path.join(root, file)
                zipf.write(
                    filePath,
                    os.path.join(zipPathPrefix,
                                 os.path.relpath(filePath, dirPath)))
Пример #12
0
class Runner:
    _scriptRunner = Inject('ScriptRunner')
    _log = Inject('Logger')
    _sys = Inject('SystemHelper')
    _varMgr = Inject('VarManager')
    _vsSolutionHelper = Inject('VisualStudioHelper')
    _prjVsSolutionHelper = Inject('ProjenyVisualStudioHelper')

    def run(self, args):
        self._args = args
        success = self._scriptRunner.runWrapper(self._runInternal)

        if not success:
            sys.exit(1)

    def _runInternal(self):
        self._log.debug("Started OpenInVisualStudio with arguments: {0}".format(" ".join(sys.argv[1:])))

        project, platform = self._getProjectAndPlatformFromFilePath(self._args.filePath)

        self._log.debug("Determined Project = {0}, Platform = {1}", project, platform)

        lineNo = 1
        if self._args.lineNo:
            lineNo = int(self._args.lineNo)

        if platform == None:
            solutionPath = None
        else:
            solutionPath = self._prjVsSolutionHelper.getCustomSolutionPath(project, platform)

        self._vsSolutionHelper.openFile(
            self._args.filePath, lineNo, solutionPath)

    def _getProjectAndPlatformFromFilePath(self, filePath):
        unityProjectsDir = self._sys.canonicalizePath(self._varMgr.expand('[UnityProjectsDir]'))
        filePath = self._sys.canonicalizePath(filePath)

        if not filePath.startswith(unityProjectsDir):
            raise Exception("The given file path is not within the UnityProjects directory")

        relativePath = filePath[len(unityProjectsDir)+1:]
        dirs = relativePath.split(os.path.sep)

        projectName = dirs[0]

        try:
            platformProjectDirName = dirs[1]
            platformDirName = platformProjectDirName[platformProjectDirName.rfind('-')+1:]

            platform = PlatformUtil.fromPlatformFolderName(platformDirName)
        except:
            platform = None

        return projectName, platform
Пример #13
0
class LogStreamFile:
    _varManager = Inject('VarManager')

    def __init__(self):
        self._fileStream = self._tryGetFileStream()

    def log(self, logType, message):

        if logType == LogType.HeadingStart:
            self._printSeperator()
            self._writeLine(message)
            self._printSeperator()
        else:
            self._writeLine(message)

    def dispose(self):
        if self._fileStream:
            self._fileStream.close()

    def _printSeperator(self):
        self._writeLine('--------------------------')

    def _writeLine(self, value):
        self._write('\n' + value)

    def _write(self, value):
        if self._fileStream:
            self._fileStream.write(value)
            self._fileStream.flush()

    def _tryGetFileStream(self):

        if not self._varManager.hasKey('LogPath'):
            return None

        primaryPath = self._varManager.expand('[LogPath]')

        previousPath = None
        if self._varManager.hasKey('LogPreviousPath'):
            previousPath = self._varManager.expand('[LogPreviousPath]')

        # Keep one old build log
        if os.path.isfile(primaryPath) and previousPath:
            shutil.copy2(primaryPath, previousPath)

        self._outputFilePath = primaryPath
        return open(primaryPath, 'a', encoding='utf-8', errors='ignore')
Пример #14
0
class ProjenyVisualStudioHelper:
    _vsHelper = Inject('VisualStudioHelper')
    _vsSolutionGenerator = Inject('VisualStudioSolutionGenerator')
    _log = Inject('Logger')
    _sys = Inject('SystemHelper')
    _packageManager = Inject('PackageManager')
    _unityHelper = Inject('UnityHelper')

    def updateCustomSolution(self, project, platform):
        self._vsSolutionGenerator.updateVisualStudioSolution(project, platform)

    def openCustomSolution(self, project, platform, filePath=None):
        self._vsHelper.openVisualStudioSolution(
            self.getCustomSolutionPath(project, platform), filePath)

    def buildCustomSolution(self, project, platform):
        solutionPath = self.getCustomSolutionPath(project, platform)

        if not self._sys.fileExists(solutionPath):
            self._log.warn(
                'Could not find generated custom solution.  Generating now.')
            self._vsSolutionGenerator.updateVisualStudioSolution(
                project, platform)

        with self._log.heading('Building {0}-{1}.sln'.format(
                project, platform)):
            self._vsHelper.buildVisualStudioProject(solutionPath, 'Debug')

    def getCustomSolutionPath(self, project, platform):
        return '[UnityProjectsDir]/{0}/{0}-{1}.sln'.format(project, platform)

    def updateUnitySolution(self, projectName, platform):
        """
        Simply runs unity and then generates the monodevelop solution file using an editor script
        This is used when generating the Visual Studio Solution to get DLL references and defines etc.
        """
        with self._log.heading(
                'Updating unity generated solution for project {0} ({1})'.
                format(projectName, platform)):
            self._packageManager.checkProjectInitialized(projectName, platform)

            # This will generate the unity csproj files which we need to generate Modest3d.sln correctly
            # It's also necessary to run this first on clean checkouts to initialize unity properly
            self._unityHelper.runEditorFunction(
                projectName, platform,
                'Projeny.ProjenyEditorUtil.ForceGenerateUnitySolution')
Пример #15
0
class ProjectConfig:
    """
    Misc. helper functions related to windows junctions
    """
    _log = Inject('Logger')

    def __init__(self):
        self.pluginsFolder = []
        self.assetsFolder = []
        self.customDirectories = {}
        self.solutionProjects = []
        self.solutionFolders = []
        self.packageFolders = []
        self.targetPlatforms = []
        self.projectSettingsPath = None

    def getAssetByName(self, name):
        for x in self.assetsFolder:
            if type(x) is dict:
                if x["name"] == name:
                    return x
            else:
                if x == name:
                    return x
Пример #16
0
class Runner:
    _log = Inject('Logger')
    _packageMgr = Inject('PackageManager')
    _schemaLoader = Inject('ProjectSchemaLoader')
    _projectConfigChanger = Inject('ProjectConfigChanger')
    _unityHelper = Inject('UnityHelper')
    _projVsHelper = Inject('ProjenyVisualStudioHelper')
    _releaseSourceManager = Inject('ReleaseSourceManager')
    _sys = Inject('SystemHelper')
    _varMgr = Inject('VarManager')

    def run(self, project, platform, requestId, param1, param2, param3):
        self._log.debug("Started EditorApi with arguments: {0}".format(
            " ".join(sys.argv[1:])))

        self._project = project
        self._platform = platform
        self._requestId = requestId
        self._param1 = param1
        self._param2 = param2
        self._param3 = param3

        succeeded = True

        # This is repeated in __main__ but this is better because
        # it will properly log detailed errors to the file log instead of to the console
        try:
            self._runInternal()
        except Exception as e:
            sys.stderr.write(str(e))
            self._log.error(str(e))

            if not MiscUtil.isRunningAsExe():
                self._log.error('\n' + traceback.format_exc())

            succeeded = False

        if not succeeded:
            sys.exit(1)

    def _outputAllPathVars(self):
        self._outputContent(
            YamlSerializer.serialize(self._varMgr.getAllParameters()))

    def _outputContent(self, value):
        self._log.noise(value)
        sys.stderr.write(value)

    def _runInternal(self):

        self._packageMgr.setPathsForProjectPlatform(self._project,
                                                    self._platform)

        if self._requestId == 'updateLinks':
            isInit = self._packageMgr.isProjectPlatformInitialized(
                self._project, self._platform)
            self._packageMgr.updateProjectJunctions(self._project,
                                                    self._platform)
            if not isInit:
                self._packageMgr.updateLinksForAllProjects()

        elif self._requestId == 'openUnity':
            self._packageMgr.checkProjectInitialized(self._project,
                                                     self._platform)
            self._unityHelper.openUnity(self._project, self._platform)

        elif self._requestId == 'getPathVars':
            self._outputAllPathVars()

        elif self._requestId == 'updateCustomSolution':
            self._projVsHelper.updateCustomSolution(self._project,
                                                    self._platform)

        elif self._requestId == 'openCustomSolution':
            self._projVsHelper.openCustomSolution(self._project,
                                                  self._platform)

        elif self._requestId == 'listPackages':
            infos = self._packageMgr.getAllPackageFolderInfos(self._project)
            for folderInfo in infos:
                self._outputContent('---\n')
                self._outputContent(
                    YamlSerializer.serialize(folderInfo) + '\n')

        elif self._requestId == 'listProjects':
            projectNames = self._packageMgr.getAllProjectNames()
            for projName in projectNames:
                self._outputContent(projName + '\n')

        elif self._requestId == 'addPackage':
            self._log.debug("Running addPackage")
            self._projectConfigChanger.addPackage(self._project, self._param1,
                                                  True)

        elif self._requestId == 'setPackages':
            self._log.debug("Running setPackage")
            packages = ast.literal_eval(self._param1)
            if isinstance(packages, list):
                packages = [n.strip() for n in packages]
                self._projectConfigChanger.setPackagesForProject(
                    self._project, packages)
            else:
                self._log.debug("failing setPackage")

        elif self._requestId == 'listReleases':
            for release in self._releaseSourceManager.lookupAllReleases():
                self._outputContent('---\n')
                self._outputContent(YamlSerializer.serialize(release) + '\n')

        elif self._requestId == 'installRelease':
            releaseName = self._param1
            packageRoot = self._param2
            versionCode = self._param3

            if versionCode == None or len(versionCode) == 0:
                versionCode = 0

            self._log.info(
                "Installing release '{0}' into package dir '{1}' with version code '{2}'",
                releaseName, packageRoot, versionCode)
            self._releaseSourceManager.installReleaseById(
                releaseName, self._project, packageRoot, versionCode, True)

        elif self._requestId == 'createProject':
            newProjName = self._param1
            duplicateSettings = (self._param2 == 'True')
            self._log.info("Creating new project '{0}'", newProjName)
            self._packageMgr.createProject(
                newProjName, self._project if duplicateSettings else None)

        elif self._requestId == 'listDependencies':
            packageName = self._param1
            schema = self._schemaLoader.loadSchema(self._project,
                                                   self._platform)
            self._outputContent(
                YamlSerializer.serialize([
                    schema.packages[x].dirPath
                    for x in schema.packages[packageName].allDependencies
                ]))

        else:
            assertThat(False, "Invalid request id '{0}'", self._requestId)
Пример #17
0
class Logger:
    _streams = InjectMany('LogStream')
    _config = Inject('Config')

    ''' Simple log class to use with build scripts '''
    def __init__(self):
        self._totalStartTime = None
        self._headingBlocks = []

        self.goodPatterns = self._getPatterns('GoodPatterns')
        self.goodMaps = self._getPatternMaps('GoodPatternMaps')

        self.infoPatterns = self._getPatterns('InfoPatterns')
        self.infoMaps = self._getPatternMaps('InfoPatternMaps')

        self.errorPatterns = self._getPatterns('ErrorPatterns')
        self.errorMaps = self._getPatternMaps('ErrorPatternMaps')

        self.warningPatterns = self._getPatterns('WarningPatterns')
        self.warningMaps = self._getPatternMaps('WarningPatternMaps')
        self.warningPatternsIgnore = self._getPatterns('WarningPatternsIgnore')

        self.debugPatterns = self._getPatterns('DebugPatterns')
        self.debugMaps = self._getPatternMaps('DebugPatternMaps')

    @property
    def totalStartTime(self):
        return self._totalStartTime

    @property
    def hasHeading(self):
        return any(self._headingBlocks)

    def getCurrentNumHeadings(self):
        return len(self._headingBlocks)

    def heading(self, message, *args):

        if not self._totalStartTime:
            self._totalStartTime = datetime.now()

        # Need to format it now so that heading gets the args
        if len(args) > 0:
            message = message.format(*args)

        block = HeadingBlock(self, message)
        self._headingBlocks.append(block)
        return block

    def noise(self, message, *args):
        self._logInternal(message, LogType.Noise, *args)

    def debug(self, message, *args):
        self._logInternal(message, LogType.Debug, *args)

    def info(self, message, *args):
        self._logInternal(message, LogType.Info, *args)

    def error(self, message, *args):

        self._logInternal(message, LogType.Error, *args)

    def warn(self, message, *args):
        self._logInternal(message, LogType.Warn, *args)

    def good(self, message, *args):
        self._logInternal(message, LogType.Good, *args)

    def _logInternal(self, message, logType, *args):

        if len(args) > 0:
            message = message.format(*args)

        newLogType, newMessage = self.classifyMessage(logType, message)

        for stream in self._streams:
            stream.log(newLogType, newMessage)

    def _getPatternMaps(self, settingName):
        maps = self._config.tryGetDictionary({}, 'Log', settingName)

        result = []
        for key, value in maps.items():
            regex = re.compile(key)
            logMap = LogMap(regex, value)
            result.append(logMap)

        return result

    def _getPatterns(self, settingName):
        patternStrings = self._config.tryGetList([], 'Log', settingName)

        result = []
        for pattern in patternStrings:
            result.append(re.compile('.*' + pattern + '.*'))

        return result

    def tryMatchPattern(self, message, maps, patterns):
        for logMap in maps:
            if logMap.regex.match(message):
                return logMap.regex.sub(logMap.sub, message)

        for pattern in patterns:
            match = pattern.match(message)

            if match:
                groups = match.groups()

                if len(groups) > 0:
                    return groups[0]

                return message

        return None

    def classifyMessage(self, logType, message):

        if logType != LogType.Noise:
            # If it is explicitly logged as something by calling for eg. log.info, use info type
            return logType, message

        parsedMessage = self.tryMatchPattern(message, self.errorMaps, self.errorPatterns)
        if parsedMessage:
            return LogType.Error, parsedMessage

        if not any(p.match(message) for p in self.warningPatternsIgnore):
            parsedMessage = self.tryMatchPattern(message, self.warningMaps, self.warningPatterns)
            if parsedMessage:
                return LogType.Warn, parsedMessage

        parsedMessage = self.tryMatchPattern(message, self.goodMaps, self.goodPatterns)
        if parsedMessage:
            return LogType.Good, parsedMessage

        parsedMessage = self.tryMatchPattern(message, self.infoMaps, self.infoPatterns)
        if parsedMessage:
            return LogType.Info, parsedMessage

        parsedMessage = self.tryMatchPattern(message, self.debugMaps, self.debugPatterns)
        if parsedMessage:
            return LogType.Debug, parsedMessage

        return LogType.Noise, message
Пример #18
0
class LogStreamConsole:
    _log = Inject('Logger')
    _sys = Inject('SystemHelper')
    _varManager = Inject('VarManager')
    _config = Inject('Config')

    def __init__(self, verbose, veryVerbose):
        self._verbose = verbose
        self._veryVerbose = veryVerbose

        self.headingPatterns = self._getPatterns('HeadingPatterns')
        self.headingMaps = self._getPatternMaps('HeadingPatternMaps')

        self.goodPatterns = self._getPatterns('GoodPatterns')
        self.goodMaps = self._getPatternMaps('GoodPatternMaps')

        self.infoPatterns = self._getPatterns('InfoPatterns')
        self.infoMaps = self._getPatternMaps('InfoPatternMaps')

        self.errorPatterns = self._getPatterns('ErrorPatterns')
        self.errorMaps = self._getPatternMaps('ErrorPatternMaps')

        self.warningPatterns = self._getPatterns('WarningPatterns')
        self.warningMaps = self._getPatternMaps('WarningPatternMaps')
        self.warningPatternsIgnore = self._getPatterns('WarningPatternsIgnore')

        self.debugPatterns = self._getPatterns('DebugPatterns')
        self.debugMaps = self._getPatternMaps('DebugPatternMaps')

        self._useColors = self._config.tryGetBool(False, 'Console',
                                                  'UseColors')

        self._fileStream = None
        if self._config.tryGetBool(False, 'Console', 'OutputToFilteredLog'):
            self._fileStream = self._getFileStream()

        if self._useColors:
            self._initColors()

    def _initColors(self):
        self._defaultColors = ColorConsole.get_text_attr()
        self._defaultBg = self._defaultColors & 0x0070
        self._defaultFg = self._defaultColors & 0x0007

    def log(self, logType, message):

        logType, message = self.classifyMessage(logType, message)

        if logType is not None:
            if logType == LogType.HeadingFailed or logType == LogType.Error:
                self._output(logType, message, sys.stderr, self._useColors)
            else:
                self._output(logType, message, sys.stdout, self._useColors)

            if self._fileStream:
                self._output(logType, message, self._fileStream, False)

    def _getFileStream(self):

        primaryPath = self._varManager.expand('[LogFilteredPath]')

        if not primaryPath:
            raise Exception("Could not find path for log file")

        previousPath = None
        if self._varManager.hasKey('LogFilteredPreviousPath'):
            previousPath = self._varManager.expand('[LogFilteredPreviousPath]')

        # Keep one old build log
        if os.path.isfile(primaryPath) and previousPath:
            shutil.copy2(primaryPath, previousPath)

        return open(primaryPath, 'w', encoding='utf-8', errors='ignore')

    def _output(self, logType, message, stream, useColors):

        stream.write('\n')

        if self._log.hasHeading and logType != LogType.Heading and logType != LogType.HeadingSucceeded and logType != LogType.HeadingFailed:
            stream.write('  ')

        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.Heading or logType == LogType.HeadingSucceeded:
            return ColorConsole.FOREGROUND_CYAN | 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 or logType == LogType.HeadingFailed:
            return ColorConsole.FOREGROUND_RED | self._defaultBg | ColorConsole.FOREGROUND_INTENSITY

        if logType == LogType.Debug:
            return ColorConsole.FOREGROUND_BLACK | self._defaultBg | ColorConsole.FOREGROUND_INTENSITY

        assertThat(False, 'Unrecognized log type "{0}"'.format(logType))

    def _getPatternMaps(self, settingName):
        maps = self._config.tryGetDictionary({}, 'Console', settingName)

        result = []
        for key, value in maps.items():
            regex = re.compile(key)
            logMap = LogMap(regex, value)
            result.append(logMap)

        return result

    def _getPatterns(self, settingName):
        patternStrings = self._config.tryGetList([], 'Console', settingName)

        result = []
        for pattern in patternStrings:
            result.append(re.compile('.*' + pattern + '.*'))

        return result

    def tryMatchPattern(self, message, maps, patterns):
        for logMap in maps:
            if logMap.regex.match(message):
                return logMap.regex.sub(logMap.sub, message)

        for pattern in patterns:
            match = pattern.match(message)

            if match:
                groups = match.groups()

                if len(groups) > 0:
                    return groups[0]

                return message

        return None

    def classifyMessage(self, logType, message):

        if logType == LogType.Info or logType == LogType.Heading or logType == LogType.HeadingFailed or logType == LogType.HeadingSucceeded or logType == LogType.Good or logType == LogType.Warn or logType == LogType.Error:
            return logType, message

        parsedMessage = self.tryMatchPattern(message, self.errorMaps,
                                             self.errorPatterns)
        if parsedMessage:
            return LogType.Error, parsedMessage

        if not any(p.match(message) for p in self.warningPatternsIgnore):
            parsedMessage = self.tryMatchPattern(message, self.warningMaps,
                                                 self.warningPatterns)
            if parsedMessage:
                return LogType.Warn, parsedMessage

        parsedMessage = self.tryMatchPattern(message, self.headingMaps,
                                             self.headingPatterns)
        if parsedMessage:
            return LogType.Heading, parsedMessage

        parsedMessage = self.tryMatchPattern(message, self.goodMaps,
                                             self.goodPatterns)
        if parsedMessage:
            return LogType.Good, parsedMessage

        parsedMessage = self.tryMatchPattern(message, self.infoMaps,
                                             self.infoPatterns)
        if parsedMessage:
            return LogType.Info, parsedMessage

        if self._verbose:
            parsedMessage = self.tryMatchPattern(message, self.debugMaps,
                                                 self.debugPatterns)
            if parsedMessage:
                return LogType.Debug, parsedMessage

        if self._veryVerbose:
            return LogType.Debug, message

        return None, message
Пример #19
0
class Runner:
    _sys = Inject('SystemHelper')
    _varMgr = Inject('VarManager')
    _log = Inject('Logger')
    _scriptRunner = Inject('ScriptRunner')
    _packageMgr = Inject('PackageManager')
    _zipHelper = Inject('ZipHelper')
    _vsSolutionHelper = Inject('VisualStudioHelper')

    def run(self, args):
        self._args = args
        success = self._scriptRunner.runWrapper(self._runInternal)

        if not success:
            sys.exit(1)

    def _copyDir(self, relativePath):
        self._sys.copyDirectory('[ProjenyDir]/' + relativePath,
                                '[TempDir]/' + relativePath)

    def _copyFile(self, relativePath):
        self._sys.copyFile('[ProjenyDir]/' + relativePath,
                           '[TempDir]/' + relativePath)

    def _runInternal(self):
        self._varMgr.add('PythonDir', PythonDir)
        self._varMgr.add('ProjenyDir', ProjenyDir)
        self._varMgr.add('SourceDir', '[ProjenyDir]/Source')
        self._varMgr.add('InstallerDir', '[ProjenyDir]/Installer')
        self._varMgr.add('TempDir', '[InstallerDir]/Build')
        self._varMgr.add('DistDir', '[InstallerDir]/Dist')

        self._sys.deleteAndReCreateDirectory('[DistDir]')
        self._sys.deleteAndReCreateDirectory('[TempDir]')

        try:
            self._updateBuildDirectory()

            versionStr = self._sys.readFileAsText(
                '[SourceDir]/Version.txt').strip()
            installerOutputPath = '[DistDir]/ProjenyInstaller-v{0}.exe'.format(
                versionStr)

            self._createInstaller(installerOutputPath)

            self._createSamplesZip(versionStr)

            if self._args.addTag:
                self._log.info('Adding git tag for version number')
                self._sys.executeAndWait(
                    "git tag -a v{0} -m 'Version {0}'".format(versionStr))

            if self._args.runInstallerAfter:
                self._sys.deleteDirectoryIfExists(
                    'C:/Program Files (x86)/Projeny')
                self._sys.executeNoWait(installerOutputPath)
        finally:
            self._sys.deleteDirectoryIfExists('[TempDir]')

    def _createSamplesZip(self, versionStr):
        with self._log.heading(
                'Clearing all generated files in Demo/UnityProjects folder'):
            self._packageMgr.clearAllProjectGeneratedFiles()

            self._sys.deleteDirectoryIfExists('[TempDir]')

            self._sys.copyDirectory('[ProjenyDir]/Demo', '[TempDir]')

            self._sys.removeFileIfExists('[TempDir]/.gitignore')
            self._sys.removeFileIfExists('[TempDir]/PrjLog.txt')

        with self._log.heading('Zipping up demo project'):
            self._zipHelper.createZipFile(
                '[TempDir]',
                '[DistDir]/ProjenySamples-v{0}.zip'.format(versionStr))

    def _createInstaller(self, installerOutputPath):
        with self._log.heading('Creating installer exe'):
            assertThat(self._sys.directoryExists(NsisPath))
            self._sys.createDirectory('[DistDir]')
            self._sys.executeAndWait(
                '"{0}" "[InstallerDir]/CreateInstaller.nsi"'.format(NsisPath))

            self._sys.renameFile('[DistDir]/ProjenyInstaller.exe',
                                 installerOutputPath)

    def _updateBuildDirectory(self):

        self._sys.deleteAndReCreateDirectory('[TempDir]')

        with self._log.heading('Building exes'):
            self._sys.executeAndWait('[PythonDir]/BuildAllExes.bat')

        with self._log.heading('Building unity plugin dlls'):
            self._vsSolutionHelper.buildVisualStudioProject(
                '[ProjenyDir]/UnityPlugin/Projeny.sln', 'Release')

            self._copyDir('UnityPlugin/Projeny/Assets')
            self._copyDir('Templates')
            self._copyFile(ConfigFileName)
            self._copyDir('Bin')

            for fileName in self._sys.getAllFilesInDirectory(
                    '[InstallerDir]/BinFiles'):
                self._sys.copyFile('[InstallerDir]/BinFiles/' + fileName,
                                   '[TempDir]/Bin/' + fileName)

            self._sys.removeByRegex('[TempDir]/Bin/UnityPlugin/Release/*.pdb')
            self._sys.deleteDirectoryIfExists(
                '[TempDir]/Bin/UnityPlugin/Debug')
class VisualStudioSolutionGenerator:
    """
    Handler for creating custom visual studio solutions based on ProjenyProject.yaml files
    """
    _log = Inject('Logger')
    _packageManager = Inject('PackageManager')
    _schemaLoader = Inject('ProjectSchemaLoader')
    _unityHelper = Inject('UnityHelper')
    _config = Inject('Config')
    _varMgr = Inject('VarManager')
    _sys = Inject('SystemHelper')

    def updateVisualStudioSolution(self, projectName, platform):
        with self._log.heading('Updating Visual Studio solution for project "{0}"'.format(projectName)):
            self._packageManager.setPathsForProjectPlatform(projectName, platform)
            self._packageManager.checkProjectInitialized(projectName, platform)

            schema = self._schemaLoader.loadSchema(projectName, platform)

            self._updateVisualStudioSolutionInternal(
                schema.packages.values(), schema.customFolderMap)

    def _prettify(self, doc):
        return minidom.parseString(ET.tostring(doc)).toprettyxml(indent="    ")

    def _getDefineConstantsElement(self, root):
        return root.findall('.//{0}DefineConstants'.format(NsPrefix))[0].text

    def _getUnityProjectReferencesItems(self, root):
        items = []
        refElems = root.findall('./{0}ItemGroup/{0}Reference'.format(NsPrefix))

        for refElem in refElems:
            name = refElem.get('Include')
            children = refElem.getchildren()

            hintPath = None

            if len(children) > 0:
                hintPathElem = children[0]
                assertThat(hintPathElem.tag == '{0}HintPath'.format(NsPrefix))

                hintPath = hintPathElem.text.replace('/', '\\')

            if hintPath:
                if not os.path.isabs(hintPath):
                    hintPath = self._varMgr.expandPath('[ProjectPlatformRoot]/{0}'.format(hintPath))

                assertThat(self._sys.fileExists(hintPath), "Expected to find file at '{0}'.  Try updating the unity generated solution, the assembly references might be out of date.".format(hintPath))

            items.append(RefInfo(name, hintPath))

        return items

    def _chooseMostRecentFile(self, path1, path2):
        path1 = self._varMgr.expandPath(path1)
        path2 = self._varMgr.expandPath(path2)

        # If they both exist choose most recent
        if self._sys.fileExists(path1) and self._sys.fileExists(path2):
            modtime1 = os.path.getmtime(path1)
            modtime2 = os.path.getmtime(path2)

            if modtime1 > modtime2:
                return path1

            return path2

        if self._sys.fileExists(path1):
            return path1

        if self._sys.fileExists(path2):
            return path2

        return None

    def _parseGeneratedUnityProject(self):

        # Annoyingly, unity does generate the solution using different paths
        # depending on settings
        # If visual studio is set to external editor, it names it the first one
        # and otherwise it names it the second one
        # So check modification times for the case where the user changes this setting
        unityProjPath = self._chooseMostRecentFile(
            '[UnityGeneratedProjectPath]', '[UnityGeneratedProjectPath2]')

        unityEditorProjPath = self._chooseMostRecentFile(
            '[UnityGeneratedProjectEditorPath]', '[UnityGeneratedProjectEditorPath2]')

        assertThat(unityProjPath and self._sys.fileExists(unityProjPath) and unityEditorProjPath and self._sys.fileExists(unityEditorProjPath), \
            'Could not find unity-generated project when generating custom solution.  This is necessary so the custom solution can add things like unity defines and DLL references within the unity project.')

        unityProjRoot = ET.parse(unityProjPath)
        unityProjEditorRoot = ET.parse(unityEditorProjPath)

        defines = self._getDefineConstantsElement(unityProjRoot)
        references = self._getUnityProjectReferencesItems(unityProjRoot)
        referencesEditor = self._getUnityProjectReferencesItems(unityProjEditorRoot)

        return UnityGeneratedProjInfo(defines, references, referencesEditor)

    def _updateVisualStudioSolutionInternal(self, allPackages, customFolderMap):

        # Necessary to avoid having ns0: prefixes everywhere on output
        ET.register_namespace('', 'http://schemas.microsoft.com/developer/msbuild/2003')

        unifyProjInfo = self._parseGeneratedUnityProject()

        projectMap = self._createProjectMap(allPackages)

        self._initDependenciesForAllProjects(
            allPackages, projectMap, unifyProjInfo)

        self._addFilesForAllProjects(
            projectMap, unifyProjInfo)

        self._writeCsProjFiles(
            projectMap, unifyProjInfo)

        self._createSolution(projectMap.values(), customFolderMap)

    def _createProjectMap(self, allPackages):
        projectMap = {}
        self._addStandardProjects(projectMap)
        self._addCustomProjects(allPackages, projectMap)
        return projectMap

    def _addStandardProjects(self, projectMap):
        projectMap[PluginsProjectName] = self._createStandardCsProjInfo(
            PluginsProjectName, '[PluginsDir]')

        projectMap[AssetsProjectName] = self._createStandardCsProjInfo(
            AssetsProjectName, '[ProjectAssetsDir]')

        projectMap[AssetsEditorProjectName] = self._createStandardCsProjInfo(
            AssetsEditorProjectName, '[ProjectAssetsDir]')

        projectMap[PluginsEditorProjectName] = self._createStandardCsProjInfo(
            PluginsEditorProjectName, '[PluginsDir]')

    def _addFilesForAllProjects(
        self, projectMap, unifyProjInfo):

        excludeDirs = []

        for projInfo in projectMap.values():
            if projInfo.packageInfo != None:
                packageDir = self._varMgr.expandPath(os.path.join(projInfo.packageInfo.outputDirVar, projInfo.packageInfo.name))
                excludeDirs.append(packageDir)

        self._initFilesForStandardCsProjForDirectory(
            projectMap[PluginsEditorProjectName], excludeDirs, unifyProjInfo, True)

        self._initFilesForStandardCsProjForDirectory(
            projectMap[PluginsProjectName], excludeDirs, unifyProjInfo, False)

        excludeDirs.append(self._varMgr.expandPath('[PluginsDir]'))

        self._initFilesForStandardCsProjForDirectory(
            projectMap[AssetsProjectName], excludeDirs, unifyProjInfo, False)

        self._initFilesForStandardCsProjForDirectory(
            projectMap[AssetsEditorProjectName], excludeDirs, unifyProjInfo, True)

    def _writeCsProjFiles(
        self, projectMap, unifyProjInfo):

        for projInfo in projectMap.values():
            if projInfo.projectType != ProjectType.Custom and projInfo.projectType != ProjectType.CustomEditor:
                continue

            if projInfo.projectType == ProjectType.CustomEditor:
                refItems = unifyProjInfo.referencesEditor
            else:
                refItems = unifyProjInfo.references

            self._writeCsProject(projInfo, projectMap, projInfo.files, refItems, unifyProjInfo.defines)

        self._writeStandardCsProjForDirectory(
            projectMap[PluginsEditorProjectName], projectMap, unifyProjInfo, True)

        self._writeStandardCsProjForDirectory(
            projectMap[PluginsProjectName], projectMap, unifyProjInfo, False)

        self._writeStandardCsProjForDirectory(
            projectMap[AssetsProjectName], projectMap, unifyProjInfo, False)

        self._writeStandardCsProjForDirectory(
            projectMap[AssetsEditorProjectName], projectMap, unifyProjInfo, True)

    def _initDependenciesForAllProjects(
        self, allPackages, projectMap, unifyProjInfo):

        for projInfo in projectMap.values():
            if projInfo.projectType != ProjectType.Custom and projInfo.projectType != ProjectType.CustomEditor:
                continue

            assertThat(projInfo.packageInfo.createCustomVsProject)

            self._log.debug('Processing generated project "{0}"'.format(projInfo.name))

            projInfo.dependencies = self._getProjectDependencies(projectMap, projInfo)

        pluginsProj = projectMap[PluginsProjectName]
        self._log.debug('Processing project "{0}"'.format(pluginsProj.name))

        prebuiltProjectInfos = [x for x in projectMap.values() if x.projectType == ProjectType.Prebuilt]

        pluginsProj.dependencies = prebuiltProjectInfos

        pluginsEditorProj = projectMap[PluginsEditorProjectName]
        pluginsEditorProj.dependencies = [pluginsProj] + prebuiltProjectInfos

        for packageInfo in allPackages:
            if packageInfo.createCustomVsProject and packageInfo.isPluginDir:
                pluginsEditorProj.dependencies.append(projectMap[packageInfo.name])

        scriptsProj = projectMap[AssetsProjectName]

        self._log.debug('Processing project "{0}"'.format(scriptsProj.name))
        scriptsProj.dependencies = [pluginsProj] + prebuiltProjectInfos

        scriptsEditorProj = projectMap[AssetsEditorProjectName]
        scriptsEditorProj.dependencies = scriptsProj.dependencies + [scriptsProj, pluginsEditorProj]

    def _addCustomProjects(
        self, allPackages, allCustomProjects):

        for packageInfo in allPackages:
            if not packageInfo.createCustomVsProject:
                continue

            if packageInfo.assemblyProjectInfo == None:
                customProject = self._createGeneratedCsProjInfo(packageInfo, False)
                allCustomProjects[customProject.name] = customProject

                customEditorProject = self._createGeneratedCsProjInfo(packageInfo, True)
                allCustomProjects[customEditorProject.name] = customEditorProject
            else:
                projId = self._getCsProjIdFromFile(packageInfo.assemblyProjectInfo.root)
                customProject = CsProjInfo(
                    projId, packageInfo.assemblyProjectInfo.path, packageInfo.name,
                    [], False, packageInfo.assemblyProjectInfo.config, ProjectType.Prebuilt, packageInfo)
                allCustomProjects[customProject.name] = customProject

    def _getCsProjIdFromFile(self, projectRoot):
        projId = projectRoot.findall('./{0}PropertyGroup/{0}ProjectGuid'.format(NsPrefix))[0].text
        return re.match('^{(.*)}$', projId).groups()[0]

    def _getFolderName(self, packageName, customFolders):

        for item in customFolders.items():
            folderName = item[0]
            pattern = item[1]
            if packageName == pattern or (pattern.startswith('/') and re.match(pattern[1:], packageName)):
                return folderName

        return None

    def _createGeneratedCsProjInfo(self, packageInfo, isEditor):

        projId = self._createProjectGuid()
        outputDir = self._varMgr.expandPath(packageInfo.outputDirVar)

        csProjectName = packageInfo.name

        if isEditor:
            csProjectName += EditorProjectNameSuffix

        outputPath = os.path.join(outputDir, csProjectName + ".csproj")

        packageDir = os.path.join(outputDir, packageInfo.name)

        files = []
        self._addCsFilesInDirectory(packageDir, [], files, isEditor, True)

        isIgnored = (len(files) == 0 or (len(files) == 1 and os.path.basename(files[0]) == PackageConfigFileName))

        return CsProjInfo(
            projId, outputPath, csProjectName, files, isIgnored, None, ProjectType.CustomEditor if isEditor else ProjectType.Custom, packageInfo)

    def _getProjectDependencies(self, projectMap, projInfo):

        packageInfo = projInfo.packageInfo
        assertIsNotNone(packageInfo)

        projDependencies = []

        isEditor = projInfo.projectType == ProjectType.CustomEditor

        if isEditor:
            projDependencies.append(projectMap[PluginsProjectName])
            projDependencies.append(projectMap[PluginsEditorProjectName])

            projDependencies.append(projectMap[packageInfo.name])

            if not packageInfo.isPluginDir:
                projDependencies.append(projectMap[AssetsProjectName])
                projDependencies.append(projectMap[AssetsEditorProjectName])
        else:
            projDependencies.append(projectMap[PluginsProjectName])

            if not packageInfo.isPluginDir:
                projDependencies.append(projectMap[AssetsProjectName])

        for dependName in packageInfo.allDependencies:
            assertThat(not dependName in projDependencies)

            if dependName in projectMap:
                dependProj = projectMap[dependName]

                projDependencies.append(dependProj)

            if isEditor:
                dependEditorName = dependName + EditorProjectNameSuffix

                if dependEditorName in projectMap:
                    dependEditorProj = projectMap[dependEditorName]

                    projDependencies.append(dependEditorProj)

        return projDependencies

    def _createSolution(self, projects, customFolderMap):

        with open(self._varMgr.expandPath('[CsSolutionTemplate]'), 'r', encoding='utf-8', errors='ignore') as inputFile:
            solutionStr = inputFile.read()

        outputPath = self._varMgr.expandPath('[SolutionPath]')
        outputDir = os.path.dirname(outputPath)

        projectList = ''
        postSolution = ''
        projectFolderMapsStr = ''

        folderIds = {}

        usedFolders = set()

        for folderName in customFolderMap:
            folderId = self._createProjectGuid()
            folderIds[folderName] = folderId

        for proj in projects:
            assertThat(proj.name)
            assertThat(proj.id)
            assertThat(proj.absPath)

            if proj.isIgnored:
                continue

            projectList += 'Project("{{{0}}}") = "{1}", "{2}", "{{{3}}}"\n' \
                .format(CsProjTypeGuid, proj.name, os.path.relpath(proj.absPath, outputDir), proj.id)

            if len(proj.dependencies) > 0:
                projectList += '\tProjectSection(ProjectDependencies) = postProject\n'
                for projDepend in proj.dependencies:
                    if not projDepend.isIgnored:
                        projectList += '\t\t{{{0}}} = {{{0}}}\n'.format(projDepend.id)
                projectList += '\tEndProjectSection\n'

            projectList += 'EndProject\n'

            if len(postSolution) != 0:
                postSolution += '\n'

            if proj.configType != None:
                buildConfig = proj.configType
            else:
                buildConfig = 'Debug'

            postSolution += \
                '\t\t{{{0}}}.Debug|Any CPU.ActiveCfg = {1}|Any CPU\n\t\t{{{0}}}.Debug|Any CPU.Build.0 = {1}|Any CPU' \
                .format(proj.id, buildConfig)

            folderName = self._getFolderName(proj.name, customFolderMap)

            if folderName:
                usedFolders.add(folderName)

                folderId = folderIds[folderName]

                if len(projectFolderMapsStr) != 0:
                    projectFolderMapsStr += '\n'

                projectFolderMapsStr += \
                    '\t\t{{{0}}} = {{{1}}}' \
                    .format(proj.id, folderId)

        projectFolderStr = ''
        for folderName, folderId in folderIds.items():
            if folderName in usedFolders:
                projectFolderStr += 'Project("{{{0}}}") = "{1}", "{2}", "{{{3}}}"\nEndProject\n' \
                    .format(SolutionFolderTypeGuid, folderName, folderName, folderId)

        solutionStr = solutionStr.replace('[ProjectList]', projectList)

        if len(postSolution.strip()) > 0:
            solutionStr = solutionStr.replace('[PostSolution]', """
    GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0}
    EndGlobalSection""".format(postSolution))
        else:
            solutionStr = solutionStr.replace('[PostSolution]', '')

        solutionStr = solutionStr.replace('[ProjectFolders]', projectFolderStr)

        if len(projectFolderMapsStr) > 0:
            fullStr = '\tGlobalSection(NestedProjects) = preSolution\n{0}\n\tEndGlobalSection'.format(projectFolderMapsStr)
            solutionStr = solutionStr.replace('[ProjectFolderMaps]', fullStr)
        else:
            solutionStr = solutionStr.replace('[ProjectFolderMaps]', '')

        with open(outputPath, 'w', encoding='utf-8', errors='ignore') as outFile:
            outFile.write(solutionStr)

        self._log.debug('Saved new solution file at "{0}"'.format(outputPath))

    def _createStandardCsProjInfo(self, projectName, outputDir):

        outputDir = self._varMgr.expandPath(outputDir)
        outputPath = os.path.join(outputDir, projectName + ".csproj")

        projId = self._createProjectGuid()

        return CsProjInfo(
            projId, outputPath, projectName, [], False, None, ProjectType.Standard, None)

    def _initFilesForStandardCsProjForDirectory(
        self, projInfo, excludeDirs, unityProjInfo, isEditor):

        outputDir = os.path.dirname(projInfo.absPath)

        projInfo.files = []
        self._addCsFilesInDirectory(outputDir, excludeDirs, projInfo.files, isEditor, False)

        # If it only contains the project config file then ignore it
        if len([x for x in projInfo.files if not x.endswith('.yaml')]) == 0:
            projInfo.isIgnored = True

    def _writeStandardCsProjForDirectory(
        self, projInfo, projectMap, unityProjInfo, isEditor):

        if projInfo.isIgnored:
            return

        if isEditor:
            references = unityProjInfo.referencesEditor
        else:
            references = unityProjInfo.references

        self._writeCsProject(
            projInfo, projectMap, projInfo.files, references, unityProjInfo.defines)

    def _createProjectGuid(self):
        return str(uuid.uuid4()).upper()

    def _shouldReferenceBeCopyLocal(self, refName):
        return refName != 'System' and refName != 'System.Core'

    def _writeCsProject(self, projInfo, projectMap, files, refItems, defines):

        outputDir = os.path.dirname(projInfo.absPath)

        doc = ET.parse(self._varMgr.expandPath('[CsProjectTemplate]'))

        root = doc.getroot()
        self._stripWhitespace(root)

        refsItemGroupElem = root.findall('./{0}ItemGroup[{0}Reference]'.format(NsPrefix))[0]
        refsItemGroupElem.clear()

        prebuiltProjectInfos = [x for x in projectMap.values() if x.projectType == ProjectType.Prebuilt]

        # Add reference items given from unity project
        for refInfo in refItems:

            if any([x for x in prebuiltProjectInfos if x.name == refInfo.name]):
                self._log.debug('Ignoring reference for prebuilt project "{0}"'.format(refInfo.name))
                continue

            refElem = ET.SubElement(refsItemGroupElem, 'Reference')
            refElem.set('Include', refInfo.name)

            if refInfo.hintPath:
                refPath = refInfo.hintPath
                assertThat(os.path.isabs(refPath), "Invalid path '{0}'".format(refPath))

                if refPath.startswith(outputDir):
                    refPath = os.path.relpath(refPath, outputDir)

                hintPathElem = ET.SubElement(refElem, 'HintPath')
                hintPathElem.text = refPath

            ET.SubElement(refElem, 'Private').text = 'True' if self._shouldReferenceBeCopyLocal(refInfo.name) else 'False'

        # Add cs files 'compile' items
        filesItemGroupElem = root.findall('./{0}ItemGroup[{0}Compile]'.format(NsPrefix))[0]
        filesItemGroupElem.clear()

        for filePath in files:
            if filePath.endswith('.cs'):
                compileElem = ET.SubElement(filesItemGroupElem, 'Compile')
            else:
                compileElem = ET.SubElement(filesItemGroupElem, 'None')

            compileElem.set('Include', os.path.relpath(filePath, outputDir))

        root.findall('./{0}PropertyGroup/{0}RootNamespace'.format(NsPrefix))[0] \
            .text = self._config.tryGetString('', 'SolutionGeneration', 'RootNamespace')

        root.findall('./{0}PropertyGroup/{0}ProjectGuid'.format(NsPrefix))[0] \
            .text = '{' + projInfo.id + '}'

        root.findall('./{0}PropertyGroup/{0}OutputPath'.format(NsPrefix))[0] \
            .text = os.path.relpath(self._varMgr.expandPath('[ProjectPlatformRoot]/Bin'), outputDir)

        root.findall('./{0}PropertyGroup/{0}AssemblyName'.format(NsPrefix))[0] \
            .text = projInfo.name

        root.findall('./{0}PropertyGroup/{0}DefineConstants'.format(NsPrefix))[0] \
            .text = defines

        tempFilesDir = os.path.relpath(self._varMgr.expandPath('[IntermediateFilesDir]'), outputDir)

        root.findall('./{0}PropertyGroup/{0}IntermediateOutputPath'.format(NsPrefix))[0] \
            .text = tempFilesDir

        root.findall('./{0}PropertyGroup/{0}BaseIntermediateOutputPath'.format(NsPrefix))[0] \
            .text = tempFilesDir

        # Add project references
        projectRefGroupElem = root.findall('./{0}ItemGroup[{0}ProjectReference]'.format(NsPrefix))[0]
        projectRefGroupElem.clear()

        for dependInfo in projInfo.dependencies:
            if dependInfo.isIgnored:
                continue

            projectRefElem = ET.SubElement(projectRefGroupElem, 'ProjectReference')
            projectRefElem.set('Include', os.path.relpath(dependInfo.absPath, outputDir))

            ET.SubElement(projectRefElem, 'Project').text = '{' + dependInfo.id + '}'
            ET.SubElement(projectRefElem, 'Name').text = dependInfo.name

        self._sys.makeMissingDirectoriesInPath(projInfo.absPath)

        with open(projInfo.absPath, 'w', encoding='utf-8', errors='ignore') as outputFile:
            outputFile.write(self._prettify(root))

    def _stripWhitespace(self, elem):
        for x in ET.ElementTree(elem).getiterator():
            if x.text: x.text = x.text.strip()
            if x.tail: x.tail = x.tail.strip()

    def _prettify(self, doc):
        return minidom.parseString(ET.tostring(doc)).toprettyxml(indent="    ")

    def _shouldIgnoreCsProjFile(self, fullPath):

        if ProjenyGeneratedDirectoryIgnorePattern.match(fullPath):
            # Never include the generated stuff
            return True

        return ProjenyDirectoryIgnorePattern.match(fullPath)

    def _addCsFilesInDirectory(self, dirPath, excludeDirs, files, isForEditor, includeYaml):
        isInsideEditorFolder = re.match(r'.*\\Editor($|\\).*', dirPath)

        if not isForEditor and isInsideEditorFolder:
            return

        if dirPath in excludeDirs:
            return

        #self._log.debug('Processing ' + dirPath)

        for excludeDir in excludeDirs:
            assertThat(not dirPath.startswith(excludeDir + "\\"))

        if not self._sys.directoryExists(dirPath):
            return

        for itemName in os.listdir(dirPath):
            fullPath = os.path.join(dirPath, itemName)

            if self._shouldIgnoreCsProjFile(fullPath):
                continue

            if os.path.isdir(fullPath):
                self._addCsFilesInDirectory(fullPath, excludeDirs, files, isForEditor, includeYaml)
            else:
                if itemName.endswith('.cs') or itemName.endswith('.txt') or (includeYaml and itemName.endswith('.yaml')):
                    if not isForEditor or isInsideEditorFolder or itemName == PackageConfigFileName:
                        files.append(fullPath)
Пример #21
0
class Runner:
    _scriptRunner = Inject('ScriptRunner')
    _log = Inject('Logger')
    _sys = Inject('SystemHelper')
    _varMgr = Inject('VarManager')
    _zipHelper = Inject('ZipHelper')
    _vsSolutionHelper = Inject('VisualStudioHelper')
    _unityHelper = Inject('UnityHelper')

    def run(self, args):
        self._args = args
        success = self._scriptRunner.runWrapper(self._runInternal)

        if not success:
            sys.exit(1)

    def _runInternal(self):
        self._varMgr.set('RootDir', RootDir)
        self._varMgr.set('BuildDir', BuildDir)
        self._varMgr.set('PythonDir', '[BuildDir]/python')
        self._varMgr.set('TempDir', '[BuildDir]/Temp')
        self._varMgr.set('AssetsDir', '[RootDir]/UnityProject/Assets')
        self._varMgr.set('ZenjectDir', '[AssetsDir]/Zenject')
        self._varMgr.set('DistDir', '[BuildDir]/Dist')
        self._varMgr.set('BinDir', '[RootDir]/AssemblyBuild/Bin')

        versionStr = self._sys.readFileAsText(
            '[ZenjectDir]/Version.txt').strip()

        self._log.info("Found version {0}", versionStr)

        self._populateDistDir(versionStr)

        if self._args.addTag:
            self._sys.executeAndReturnOutput(
                "git tag -a v{0} -m 'Version {0}'".format(versionStr))
            self._log.info(
                "Incremented version to {0}! New tag was created as well",
                versionStr)
        else:
            self._log.info(
                "Incremented version to {0}!  Dist directory contains the releases.  NOTE: No tags were created however",
                versionStr)

    def _populateDistDir(self, versionStr):

        self._sys.deleteDirectoryIfExists('[DistDir]')
        self._sys.createDirectory('[DistDir]')

        self._sys.deleteDirectoryIfExists('[TempDir]')
        self._sys.createDirectory('[TempDir]')

        try:
            self._createCSharpPackage(
                True,
                '[DistDir]/Zenject-WithAsteroidsDemo@v{0}.unitypackage'.format(
                    versionStr))
            self._createCSharpPackage(
                False,
                '[DistDir]/Zenject@v{0}.unitypackage'.format(versionStr))

            self._createDllPackage(
                '[DistDir]/Zenject-BinariesOnly@v{0}.unitypackage'.format(
                    versionStr))

            self._createNonUnityZip(
                '[DistDir]/Zenject-NonUnity@v{0}.zip'.format(versionStr))
        finally:
            self._sys.deleteDirectory('[TempDir]')

    def _createNonUnityZip(self, zipPath):

        self._log.heading('Creating non unity zip')

        tempDir = '[TempDir]/NonUnity'
        self._sys.createDirectory(tempDir)
        self._sys.clearDirectoryContents(tempDir)

        binDir = '[BinDir]/Not Unity Release'
        self._sys.deleteDirectoryIfExists(binDir)
        self._sys.createDirectory(binDir)
        self._vsSolutionHelper.buildVisualStudioProject(
            '[RootDir]/AssemblyBuild/Zenject.sln', 'Not Unity Release')

        self._log.info('Copying Zenject dlls')
        self._sys.copyFile('{0}/Zenject.dll'.format(binDir),
                           '{0}/Zenject.dll'.format(tempDir))
        self._sys.copyFile('{0}/Zenject.Commands.dll'.format(binDir),
                           '{0}/Zenject.Commands.dll'.format(tempDir))

        self._zipHelper.createZipFile(tempDir, zipPath)

    def _createDllPackage(self, outputPath):

        self._log.heading('Creating {0}'.format(os.path.basename(outputPath)))

        self._varMgr.set('PackageTempDir', '[TempDir]/Packager')
        self._varMgr.set('ZenTempDir', '[PackageTempDir]/Assets/Zenject')

        self._sys.createDirectory('[PackageTempDir]')
        self._sys.createDirectory('[PackageTempDir]/ProjectSettings')

        try:
            self._log.info('Building zenject dlls')

            self._varMgr.set('ZenDllDir', '[BinDir]/Release')
            self._varMgr.set('ZenDllMetaDir', '[BuildDir]/BinaryMetas')

            self._sys.deleteDirectoryIfExists('[ZenDllDir]')
            self._sys.createDirectory('[ZenDllDir]')

            self._vsSolutionHelper.buildVisualStudioProject(
                '[RootDir]/AssemblyBuild/Zenject.sln', 'Release')

            self._log.info('Copying Zenject dlls')

            self._sys.copyFile('[ZenDllDir]/Zenject.dll',
                               '[ZenTempDir]/Zenject.dll')
            self._sys.copyFile('[ZenDllMetaDir]/Zenject.dll.meta',
                               '[ZenTempDir]/Zenject.dll.meta')

            self._sys.copyFile('[ZenDllDir]/Zenject-editor.dll',
                               '[ZenTempDir]/Editor/Zenject-editor.dll')
            self._sys.copyFile('[ZenDllMetaDir]/Zenject-editor.dll.meta',
                               '[ZenTempDir]/Editor/Zenject-editor.dll.meta')

            self._sys.copyFile('[ZenDllDir]/Zenject.Commands.dll',
                               '[ZenTempDir]/Zenject.Commands.dll')
            self._sys.copyFile('[ZenDllMetaDir]/Zenject.Commands.dll.meta',
                               '[ZenTempDir]/Zenject.Commands.dll.meta')

            self._sys.copyFile('[ZenjectDir]/Version.txt',
                               '[ZenTempDir]/Version.txt')
            self._sys.copyFile('[ZenjectDir]/Version.txt.meta',
                               '[ZenTempDir]/Version.txt.meta')

            self._sys.copyFile('[ZenjectDir]/LICENSE.txt',
                               '[ZenTempDir]/LICENSE.txt')
            self._sys.copyFile('[ZenjectDir]/LICENSE.txt.meta',
                               '[ZenTempDir]/LICENSE.txt.meta')

            self._sys.copyDirectory('[ZenjectDir]/Documentation',
                                    '[ZenTempDir]/Documentation')
            self._sys.copyFile('[ZenjectDir]/Documentation.meta',
                               '[ZenTempDir]/Documentation.meta')

            self._createUnityPackage('[PackageTempDir]', outputPath)
        finally:
            self._sys.deleteDirectory('[PackageTempDir]')

        self._log.heading('Creating {0}'.format(os.path.basename(outputPath)))

    def _createCSharpPackage(self, includeSample, outputPath):

        self._log.heading('Creating {0}'.format(os.path.basename(outputPath)))

        self._varMgr.set('PackageTempDir', '[TempDir]/Packager')
        self._varMgr.set('ZenTempDir', '[PackageTempDir]/Assets/Zenject')

        self._sys.createDirectory('[PackageTempDir]')
        self._sys.createDirectory('[PackageTempDir]/ProjectSettings')

        try:
            self._log.info('Copying Zenject to temporary directory')
            self._sys.copyDirectory('[ZenjectDir]', '[ZenTempDir]')

            self._log.info('Cleaning up Zenject directory')
            self._zipHelper.createZipFile(
                '[ZenTempDir]/OptionalExtras/UnitTests',
                '[ZenTempDir]/OptionalExtras/UnitTests.zip')
            self._sys.deleteDirectory('[ZenTempDir]/OptionalExtras/UnitTests')
            self._sys.removeFile('[ZenTempDir]/OptionalExtras/UnitTests.meta')

            self._zipHelper.createZipFile(
                '[ZenTempDir]/OptionalExtras/IntegrationTests',
                '[ZenTempDir]/OptionalExtras/IntegrationTests.zip')
            self._sys.deleteDirectory(
                '[ZenTempDir]/OptionalExtras/IntegrationTests')
            self._sys.removeFile(
                '[ZenTempDir]/OptionalExtras/IntegrationTests.meta')

            self._zipHelper.createZipFile(
                '[ZenTempDir]/OptionalExtras/AutoMocking',
                '[ZenTempDir]/OptionalExtras/AutoMocking.zip')
            self._sys.deleteDirectory(
                '[ZenTempDir]/OptionalExtras/AutoMocking')
            self._sys.removeFile(
                '[ZenTempDir]/OptionalExtras/AutoMocking.meta')

            self._sys.removeFile('[ZenTempDir]/Source/Zenject.csproj')
            self._sys.removeFile('[ZenTempDir]/Source/Zenject.csproj.user')
            self._sys.removeFile(
                '[ZenTempDir]/OptionalExtras/CommandsAndSignals/Zenject.Commands.csproj'
            )
            self._sys.removeFile(
                '[ZenTempDir]/OptionalExtras/CommandsAndSignals/Zenject.Commands.csproj.user'
            )

            if not includeSample:
                self._sys.deleteDirectory(
                    '[ZenTempDir]/OptionalExtras/SampleGame')

            self._createUnityPackage('[PackageTempDir]', outputPath)
        finally:
            self._sys.deleteDirectory('[PackageTempDir]')

    def _createUnityPackage(self, projectPath, outputPath):
        self._sys.copyFile(
            '[BuildDir]/UnityPackager/UnityPackageUtil.cs',
            '{0}/Assets/Editor/UnityPackageUtil.cs'.format(projectPath))

        self._log.info('Running unity to create unity package')

        self._unityHelper.runEditorFunction(
            '[PackageTempDir]', 'Zenject.UnityPackageUtil.CreateUnityPackage')
        self._sys.move('{0}/Zenject.unitypackage'.format(projectPath),
                       outputPath)
Пример #22
0
class ProjectSchemaLoader:
    _varMgr = Inject('VarManager')
    _log = Inject('Logger')
    _sys = Inject('SystemHelper')

    def loadSchema(self, name, platform):
        try:
            return self._loadSchemaInternal(name, platform)
        except Exception as e:
            raise Exception("Failed while processing config yaml for project '{0}' (platform '{1}'). Details: {2}".format(name, platform, str(e))) from e

    def loadProjectConfig(self, name):
        schemaPath = self._varMgr.expandPath('[UnityProjectsDir]/{0}/{1}'.format(name, ProjectConfigFileName))
        schemaPathUser = self._varMgr.expandPath('[UnityProjectsDir]/{0}/{1}'.format(name, ProjectUserConfigFileName))
        schemaPathGlobal = self._varMgr.expandPath('[UnityProjectsDir]/{0}'.format(ProjectConfigFileName))
        schemaPathUserGlobal = self._varMgr.expandPath('[UnityProjectsDir]/{0}'.format(ProjectUserConfigFileName))

        self._log.debug('Loading schema at path "{0}"'.format(schemaPath))
        yamlConfig = Config(loadYamlFilesThatExist(schemaPath, schemaPathUser, schemaPathGlobal, schemaPathUserGlobal))

        config = ProjectConfig()

        config.pluginsFolder = yamlConfig.tryGetList([], 'PluginsFolder')
        config.assetsFolder = yamlConfig.tryGetList([], 'AssetsFolder')
        config.solutionProjects = yamlConfig.tryGetList([], 'SolutionProjects')
        config.targetPlatforms = yamlConfig.tryGetList([Platforms.Windows], 'TargetPlatforms')
        config.solutionFolders = yamlConfig.tryGetOrderedDictionary(OrderedDict(), 'SolutionFolders')
        config.packageFolders = yamlConfig.getList('PackageFolders')
        config.projectSettingsPath = yamlConfig.getString('ProjectSettingsPath')

        # Remove duplicates
        config.assetsFolder = list(set(config.assetsFolder))
        config.pluginsFolder = list(set(config.pluginsFolder))

        for packageName in config.pluginsFolder:
            assertThat(not packageName in config.assetsFolder, "Found package '{0}' in both scripts and plugins.  Must be in only one or the other".format(packageName))

        return config

    def _loadSchemaInternal(self, name, platform):

        config = self.loadProjectConfig(name)

        # Search all the given packages and any new packages that are dependencies and create PackageInfo() objects for each
        packageMap = self._getAllPackageInfos(config, platform)

        self._addGroupedDependenciesAsExplicitDependencies(packageMap)

        self._ensurePrebuiltProjectsHaveNoScripts(packageMap)

        self._ensurePrebuiltProjectDependenciesArePrebuilt(packageMap)

        # We have all the package infos, but we don't know which packages depend on what so calculate that
        self._calculateDependencyListForEachPackage(packageMap)

        # For the pre-built assembly projects, if we add one of them to our solution,
        # then we need to add all the pre-built dependencies, since unlike generated projects
        # we can't make the prebuilt projects use the dll directly
        self._ensureVisiblePrebuiltProjectHaveVisibleDependencies(packageMap)

        self._printDependencyTree(packageMap)

        for customProj in config.solutionProjects:
            assertThat(customProj.startswith('/') or customProj in packageMap, 'Given project "{0}" in schema is not included in either "scripts" or "plugins"'.format(customProj))

        self._log.debug('Found {0} packages in total for given schema'.format(len(packageMap)))

        # In Unity, the plugins folder can not have any dependencies on anything in the scripts folder
        # So if dependencies exist then just automatically move those packages to the scripts folder
        self._ensurePluginPackagesDoNotHaveDependenciesInAssets(packageMap)

        self._ensurePackagesThatAreNotProjectsDoNotHaveProjectDependencies(packageMap)

        for info in packageMap.values():
            if info.forcePluginsDir and not info.isPluginDir:
                assertThat(False, "Package '{0}' must be in plugins directory".format(info.name))

        self._ensureAllPackagesExist(packageMap)

        return ProjectSchema(name, packageMap, config.solutionFolders, config.projectSettingsPath, platform, config.targetPlatforms)

    def _shouldIncludeForPlatform(self, packageName, packageConfig, folderType, platform):

        if folderType == FolderTypes.AndroidProject or folderType == FolderTypes.AndroidLibraries:
            allowedPlatforms = [Platforms.Android]
        elif folderType == FolderTypes.Ios:
            allowedPlatforms = [Platforms.Ios]
        elif folderType == FolderTypes.WebGl:
            allowedPlatforms = [Platforms.WebGl]
        else:
            allowedPlatforms = packageConfig.tryGetList([], 'Platforms')

            if len(allowedPlatforms) == 0:
                return True

        if platform not in allowedPlatforms:
            self._log.debug("Skipped project '{0}' since it is not enabled for platform '{1}'".format(packageName, platform))
            return False

        return True

    def _getFolderTypeFromString(self, value):
        value = value.lower()

        if not value or value == FolderTypes.Normal or len(value) == 0:
            return FolderTypes.Normal

        if value == FolderTypes.AndroidProject:
            return FolderTypes.AndroidProject

        if value == FolderTypes.AndroidLibraries:
            return FolderTypes.AndroidLibraries

        if value == FolderTypes.Ios:
            return FolderTypes.Ios

        if value == FolderTypes.WebGl:
            return FolderTypes.WebGl

        if value == FolderTypes.StreamingAssets:
            return FolderTypes.StreamingAssets

        assertThat(False, "Unrecognized folder type '{0}'".format(value))
        return ""

    def _getAllPackageInfos(self, projectConfig, platform):
        configRefDesc = "'{0}' or '{1}'".format(ProjectConfigFileName, ProjectUserConfigFileName)
        allPackageRefs = [PackageReference(x, configRefDesc) for x in projectConfig.pluginsFolder + projectConfig.assetsFolder]

        packageMap = {}

        # Resolve all dependencies for each package
        # by default, put any dependencies that are not declared explicitly into the plugins folder
        for packageRef in allPackageRefs:

            packageName = packageRef.name
            packageDir = None

            for packageFolder in projectConfig.packageFolders:
                candidatePackageDir = os.path.join(packageFolder, packageName)

                if self._sys.directoryExists(candidatePackageDir):
                    packageDir = self._varMgr.expandPath(candidatePackageDir)
                    break

            assertIsNotNone(packageDir, "Could not find package '{0}' in any of the package directories!  Referenced in {1}", packageName, packageRef.sourceDesc)

            configPath = os.path.join(packageDir, PackageConfigFileName)

            if os.path.exists(configPath):
                packageConfig = Config(loadYamlFilesThatExist(configPath))
            else:
                packageConfig = Config([])

            folderType = self._getFolderTypeFromString(packageConfig.tryGetString('', 'FolderType'))

            if not self._shouldIncludeForPlatform(packageName, packageConfig, folderType, platform):
                continue

            createCustomVsProject = self._shouldCreateVsProjectForName(packageName, projectConfig.solutionProjects)

            isPluginsDir = True

            if packageName in projectConfig.assetsFolder:
                assertThat(not packageName in projectConfig.pluginsFolder)
                isPluginsDir = False

            if packageConfig.tryGetBool(False, 'ForceAssetsDirectory'):
                isPluginsDir = False

            explicitDependencies = packageConfig.tryGetList([], 'Dependencies')

            forcePluginsDir = packageConfig.tryGetBool(False, 'ForcePluginsDirectory')

            assemblyProjInfo = self._tryGetAssemblyProjectInfo(packageConfig, packageName)

            sourceDesc = '"{0}"'.format(configPath)

            if assemblyProjInfo != None:
                for assemblyDependName in assemblyProjInfo.dependencies:
                    if assemblyDependName not in [x.name for x in allPackageRefs]:
                        allPackageRefs.append(PackageReference(assemblyDependName, sourceDesc))

                explicitDependencies += assemblyProjInfo.dependencies

            groupedDependencies = packageConfig.tryGetList([], 'GroupWith')
            extraDependencies = packageConfig.tryGetList([], 'Extras')

            assertThat(not packageName in packageMap, "Found duplicate package with name '{0}'", packageName)

            packageMap[packageName] = PackageInfo(
                isPluginsDir, packageName, packageConfig, createCustomVsProject,
                explicitDependencies, forcePluginsDir, folderType, assemblyProjInfo, packageDir, groupedDependencies)

            for dependName in (explicitDependencies + groupedDependencies + extraDependencies):
                if dependName not in [x.name for x in allPackageRefs]:
                    # Yes, python is ok with changing allPackageRefs even while iterating over it
                    allPackageRefs.append(PackageReference(dependName, sourceDesc))

        return packageMap

    def _tryGetAssemblyProjectInfo(self, packageConfig, packageName):
        assemblyProjectRelativePath = packageConfig.tryGetString(None, 'AssemblyProject', 'Path')

        if assemblyProjectRelativePath == None:
            return None

        projFullPath = self._varMgr.expand(assemblyProjectRelativePath)

        if not os.path.isabs(projFullPath):
            projFullPath = os.path.join(packageDir, assemblyProjectRelativePath)

        assertThat(self._sys.fileExists(projFullPath), "Expected to find file at '{0}'.", projFullPath)

        projAnalyzer = CsProjAnalyzer(projFullPath)

        assemblyName = projAnalyzer.getAssemblyName()
        assertThat(assemblyName == '$(MSBuildProjectName)' or assemblyName.lower() == packageName.lower(), 'Packages that represent assembly projects must have the same name as the assembly')

        assertIsEqual(self._sys.getFileNameWithoutExtension(projFullPath).lower(), packageName.lower(),
          'Assembly projects must have the same name as their package')

        projConfig = packageConfig.tryGetString(None, 'AssemblyProject', 'Config')
        dependencies = projAnalyzer.getProjectReferences()

        return AssemblyProjectInfo(
            projFullPath, projAnalyzer.root, projConfig, dependencies)

    def getDependenciesFromCsProj(self, projectRoot):
        result = []
        for projRef in projectRoot.findall('./{0}ItemGroup/{0}ProjectReference/{0}Name'.format(NsPrefix)):
            result.append(projRef.text)
        return result

    def _ensureAllPackagesExist(self, packageMap):
        for package in packageMap.values():
            assertThat(self._sys.directoryExists(package.dirPath),
               "Could not find directory for package '{0}'", package.name)

    def _ensureVisiblePrebuiltProjectHaveVisibleDependencies(self, packageMap):
        for package in packageMap.values():
            if package.assemblyProjectInfo != None and package.createCustomVsProject:
                self._makeAllPrebuiltDependenciesVisible(package, packageMap)

    def _makeAllPrebuiltDependenciesVisible(self, package, packageMap):
        for dependName in package.explicitDependencies:
            depend = packageMap[dependName]

            if not depend.createCustomVsProject:
                depend.createCustomVsProject = True
                self._makeAllPrebuiltDependenciesVisible(depend, packageMap)

    def _ensurePrebuiltProjectDependenciesArePrebuilt(self, packageMap):
        for packageInfo in packageMap.values():
            assInfo = packageInfo.assemblyProjectInfo

            if assInfo == None:
                continue

            for dependName in assInfo.dependencies:
                depend = packageMap[dependName]
                assertThat(depend.assemblyProjectInfo != None,
                   "Expected package '{0}' to have an assembly project defined, since another assembly project ({1}) depends on it", dependName, packageInfo.name)

    def _ensurePrebuiltProjectsHaveNoScripts(self, packageMap):
        for package in packageMap.values():
            if package.assemblyProjectInfo != None:
                assertThat(not any(self._sys.findFilesByPattern(package.dirPath, '*.cs')),
                   "Found C# scripts in assembly project '{0}'.  This is not allowed - please move to a separate package.", package.name)

    def _ensurePackagesThatAreNotProjectsDoNotHaveProjectDependencies(self, packageMap):
        changedOne = True

        while changedOne:
            changedOne = False

            for info in packageMap.values():
                if not info.createCustomVsProject and self._hasVsProjectDependency(info, packageMap):
                    info.createCustomVsProject = True
                    self._log.debug('Created visual studio project for {0} package even though it wasnt marked as one, because it has csproj dependencies'.format(info.name))
                    changedOne = True

    def _hasVsProjectDependency(self, info, packageMap):
        for dependName in info.allDependencies:
            if not dependName in packageMap:
                # For eg. a platform specific dependency
                continue

            dependInfo = packageMap[dependName]

            if dependInfo.createCustomVsProject:
                return True

        return False

    def _ensurePluginPackagesDoNotHaveDependenciesInAssets(self, packageMap):
        movedProject = True

        while movedProject:
            movedProject = False

            for info in packageMap.values():
                if info.isPluginDir and self._hasAssetsDependency(info, packageMap):
                    info.isPluginDir = False
                    self._log.debug('Moved {0} package to scripts folder since it has dependencies there and therefore cannot be in plugins'.format(info.name))
                    movedProject = True

    def _hasAssetsDependency(self, info, packageMap):
        for dependName in info.allDependencies:
            if not dependName in packageMap:
                # For eg. a platform specific dependency
                continue

            dependInfo = packageMap[dependName]

            if not dependInfo.isPluginDir:
                return True

        return False

    def _printDependencyTree(self, packageMap):
        packages = sorted(packageMap.values(), key = lambda p: (p.isPluginDir, -len(p.explicitDependencies)))

        done = {}

        for pack in packages:
            self._printDependency(pack, done, 1, packageMap)

    def _printDependency(self, package, done, indentCount, packageMap):
        done[package.name] = True

        indentInterval = '    '

        indent = ((indentCount - 1) * (indentInterval + '.')) + indentInterval
        self._log.debug(indent + '|-' + package.name)

        for dependName in package.explicitDependencies:
            if dependName in packageMap:
                subPackage = packageMap[dependName]

                if subPackage.name in done:
                    self._log.debug(indent + '.' + indentInterval + '|~' + subPackage.name)
                else:
                    self._printDependency(subPackage, done, indentCount+1, packageMap)

    def _shouldCreateVsProjectForName(self, packageName, solutionProjects):
        if packageName in solutionProjects:
            return True

        # Allow regex's!
        for projPattern in solutionProjects:
            if projPattern.startswith('/'):
                projPattern = projPattern[1:]
                try:
                    if re.match(projPattern, packageName):
                        return True
                except Exception as e:
                    raise Exception("Failed while parsing project regex '/{0}' from {1}/{2}.  Details: {3}".format(projPattern, self._varMgr.expand('ProjectName'), ProjectConfigFileName, str(e)))

        return False

    def _addGroupedDependenciesAsExplicitDependencies(self, packageMap):

        # There is a bug here where it won't handle grouped dependencies within grouped dependencies
        for info in packageMap.values():
            extras = set()

            for explicitDependName in info.explicitDependencies:
                if explicitDependName not in packageMap:
                    continue

                explicitDependInfo = packageMap[explicitDependName]

                for groupedDependName in explicitDependInfo.groupedDependencies:
                    if info.name != groupedDependName:
                        extras.add(groupedDependName)

            info.explicitDependencies += list(extras)

    def _calculateDependencyListForEachPackage(self, packageMap):

        self._log.debug('Processing dependency tree')

        inProgress = set()

        for info in packageMap.values():
            self._calculateDependencyListForPackage(info, packageMap, inProgress)

    def _calculateDependencyListForPackage(self, packageInfo, packageMap, inProgress):

        if packageInfo.name in inProgress:
            assertThat(False, "Found circular dependency when processing package {0}.  Dependency list: {1}".format(packageInfo.name, ' -> '.join([x for x in inProgress]) + '-> ' + packageInfo.name))

        inProgress.add(packageInfo.name)
        allDependencies = set(packageInfo.explicitDependencies)

        for explicitDependName in packageInfo.explicitDependencies:
            if explicitDependName not in packageMap:
                # This can happen if a package depends on another package that is platform specific
                continue

            explicitDependInfo = packageMap[explicitDependName]

            if explicitDependInfo.allDependencies == None:
                self._calculateDependencyListForPackage(explicitDependInfo, packageMap, inProgress)

            for dependName in explicitDependInfo.allDependencies:
                allDependencies.add(dependName)

        packageInfo.allDependencies = list(allDependencies)
        inProgress.remove(packageInfo.name)
Пример #23
0
class SystemHelper:
    '''Responsibilities:
        - Miscellaneous file-handling/path-related operations
        - Wrapper to execute arbitrary commands
    '''
    _varManager = Inject('VarManager')
    _log = Inject('Logger')
    _processRunner = Inject('ProcessRunner')

    # Use an hour timeout
    def __init__(self, timeout = 60 * 60):
        self._timeout = timeout

    def canonicalizePath(self, pathStr):
        # Make one standard representation of the given path
        # This will remove ..\ and also change to always use back slashes since this is what os.path.join etc. uses
        return self._varManager.expandPath(pathStr)

    def executeAndWait(self, commandStr, startDir = None):
        expandedStr = self._varManager.expand(commandStr)

        self._log.debug("Executing '%s'" % expandedStr)

        vals = self._splitCommandStr(expandedStr)

        if startDir != None:
            startDir = self._varManager.expand(startDir)

        result = self._processRunner.waitForProcessOrTimeout(vals, self._timeout, startDir)

        if result == ResultType.Error:
            raise ProcessErrorCodeException('Command returned with error code while executing: %s' % expandedStr)

        if result == ResultType.TimedOut:
            raise ProcessTimeoutException('Timed out while waiting for command: %s' % expandedStr)

        assertThat(result == ResultType.Success)

    def executeNoWait(self, commandStr, startDir = None):
        expandedStr = self._varManager.expand(commandStr)

        self._log.debug("Executing '{0}'".format(expandedStr))

        vals = self._splitCommandStr(expandedStr)

        if startDir != None:
            startDir = self._varManager.expand(startDir)

        self._processRunner.execNoWait(vals, startDir)

    # This is only used to execute shell-specific commands like copy, mklink, etc.
    def executeShellCommand(self, commandStr, startDir = None):
        expandedStr = self._varManager.expand(commandStr)

        self._log.debug("Executing '%s'" % expandedStr)

        if startDir != None:
            startDir = self._varManager.expand(startDir)

        result = self._processRunner.execShellCommand(expandedStr, startDir)

        if result == ResultType.Error:
            raise ProcessErrorCodeException('Command returned with error code while executing: %s' % expandedStr)

        assertThat(result == ResultType.Success, "Expected success result but found '{0}'".format(result))

    def _splitCommandStr(self, commandStr):
        # Hacky but necessary since shlex.split will otherwise remove our backslashes
        if platform.platform().startswith('Windows'):
            commandStr = commandStr.replace(os.sep, os.sep + os.sep)

        # Convert command to argument list to avoid issues with escape characters, etc.
        # Based on an answer here: http://stackoverflow.com/questions/12081970/python-using-quotes-in-the-subprocess-popen
        return shlex.split(commandStr)

    def executeAndReturnOutput(self, commandStr):
        self._log.debug("Executing '%s'" % commandStr)
        return subprocess.getoutput(self._splitCommandStr(commandStr)).strip()

    def walkDir(self, dirPath):
        dirPath = self._varManager.expand(dirPath)
        return os.listdir(dirPath)

    def getParentDirectoriesWithSelf(self, path):
        yield path

        for parentDir in self.getParentDirectories(path):
            yield parentDir

    def getParentDirectories(self, path):
        path = self._varManager.expand(path)

        lastParentDir = None
        parentDir = os.path.dirname(path)

        while parentDir and parentDir != lastParentDir:
            yield parentDir

            lastParentDir = parentDir
            parentDir = os.path.dirname(parentDir)

    def createDirectory(self, dirPath):
        dirPath = self._varManager.expand(dirPath)
        assertThat(not self.directoryExists(dirPath), 'Tried to create a directory that already exists')
        os.makedirs(dirPath)

    def makeMissingDirectoriesInPath(self, dirPath):
        dirPath = self._varManager.expand(dirPath)
        try:
            os.makedirs(os.path.dirname(dirPath))
        except:
            pass

    def copyFile(self, fromPath, toPath):
        toPath = self._varManager.expand(toPath)
        fromPath = self._varManager.expand(fromPath)

        self.makeMissingDirectoriesInPath(toPath)
        shutil.copy2(fromPath, toPath)

    def move(self, fromPath, toPath):
        toPath = self._varManager.expand(toPath)
        fromPath = self._varManager.expand(fromPath)

        self.makeMissingDirectoriesInPath(toPath)
        shutil.move(fromPath, toPath)

    def IsDir(self, path):
        return os.path.isdir(self._varManager.expand(path))

    def clearDirectoryContents(self, dirPath):
        dirPath = self._varManager.expand(dirPath)

        if not os.path.exists(dirPath):
            return

        for fileName in os.listdir(dirPath):
            filePath = os.path.join(dirPath, fileName)
            if os.path.isfile(filePath):
                os.unlink(filePath)
            elif os.path.isdir(filePath):
                shutil.rmtree(filePath)

    def deleteDirectoryWaitIfNecessary(self, dirPath):
        dirPath = self._varManager.expand(dirPath)

        if not os.path.isdir(dirPath):
            # Already removed
            return

        attemptsLeft = 10

        while True:
            try:
                shutil.rmtree(dirPath)
            except Exception as e:
                self._log.warn('Could not delete directory at "{0}".  Waiting to try again...'.format(dirPath))
                time.sleep(5)
                attemptsLeft -= 1

                if attemptsLeft < 0:
                    raise e
                continue
            break

    def deleteDirectory(self, dirPath):
        dirPath = self._varManager.expand(dirPath)
        shutil.rmtree(dirPath)

    def deleteDirectoryIfExists(self, dirPath):
        dirPath = self._varManager.expand(dirPath)

        if os.path.exists(dirPath):
            shutil.rmtree(dirPath)
            return True

        return False

    def deleteEmptyDirectoriesUnder(self, dirPath):
        dirPath = self._varManager.expandPath(dirPath)

        if not os.path.isdir(dirPath):
            return 0

        # Can't process long paths on windows
        if len(dirPath) >= 256:
            return 0

        files = os.listdir(dirPath)

        numDirsDeleted = 0

        for fileName in files:
            fullpath = os.path.join(dirPath, fileName)

            if os.path.isdir(fullpath):
                numDirsDeleted += self.deleteEmptyDirectoriesUnder(fullpath)

        files = os.listdir(dirPath)

        if len(files) == 0:
            self._log.debug("Removing empty folder '%s'" % dirPath)
            os.rmdir(dirPath)
            numDirsDeleted += 1

            metaFilePath = dirPath + '/../' + os.path.basename(dirPath) + '.meta'

            if os.path.isfile(metaFilePath):
                self._log.debug("Removing meta file '%s'" % metaFilePath)
                os.remove(metaFilePath)

        return numDirsDeleted

    def fileExists(self, path):
        return os.path.isfile(self._varManager.expand(path))

    def directoryExists(self, dirPath):
        return os.path.exists(self._varManager.expand(dirPath))

    def copyDirectory(self, fromPath, toPath):
        fromPath = self._varManager.expand(fromPath)
        toPath = self._varManager.expand(toPath)

        self._log.debug("Copying directory '{0}' to '{1}'".format(fromPath, toPath))

        shutil.copytree(fromPath, toPath)

    def readFileAsText(self, path):
        with self.openInputFile(path) as f:
            return f.read()

    def writeFileAsText(self, path, text):
        with self.openOutputFile(path) as f:
            f.write(text)

    def openOutputFile(self, path):
        path = self._varManager.expand(path)
        self.makeMissingDirectoriesInPath(path)
        return open(path, 'w', encoding='utf-8', errors='ignore')

    def openInputFile(self, path):
        return open(self._varManager.expand(path), 'r', encoding='utf-8', errors='ignore')

    def removeFile(self, fileName):
        os.remove(self._varManager.expand(fileName))

    def removeFileIfExists(self, fileName):
        fullPath = self._varManager.expand(fileName)

        if os.path.isfile(fullPath):
            os.remove(fullPath)
            return True

        return False

    def findFilesByPattern(self, startDir, pattern):
        startDir = self._varManager.expand(startDir)

        for root, dirs, files in os.walk(startDir):
            for basename in files:
                if fnmatch.fnmatch(basename, pattern):
                    filename = os.path.join(root, basename)
                    yield filename

    def renameFile(self, currentName, newName):
        os.rename(self._varManager.expand(currentName), self._varManager.expand(newName))

    def removeFileWaitIfNecessary(self, fileName):
        outputPath = self._varManager.expand(fileName)

        if not os.path.isfile(outputPath):
            # File already removed
            return

        while True:
            try:
                os.remove(outputPath)
            except OSError:
                self._log.warn('Could not delete file at "{0}".  Waiting to try again...'.format(outputPath))
                time.sleep(5)
                continue
            break

    def removeByRegex(self, regex):
        regex = self._varManager.expand(regex)
        count = 0

        for filePath in glob(regex):
            os.unlink(filePath)
            count += 1

        self._log.debug("Removed %s files matching '%s'" % (count, regex))

    def makeMissingDirectoriesInPath(self, dirPath):
        dirPath = self._varManager.expand(dirPath)
        self._log.debug("Making missing directories in path '{0}'".format(dirPath))
        try:
            os.makedirs(os.path.dirname(dirPath))
        except:
            pass
Пример #24
0
class VisualStudioHelper:
    _log = Inject('Logger')
    _config = Inject('Config')
    _packageManager = Inject('PackageManager')
    _unityHelper = Inject('UnityHelper')
    _varMgr = Inject('VarManager')
    _sys = Inject('SystemHelper')
    _vsSolutionGenerator = Inject('VisualStudioSolutionGenerator')

    def openFile(self, filePath, lineNo, project, platform):
        if not lineNo or lineNo <= 0:
            lineNo = 1

        if MiscUtil.doesProcessExist('^devenv\.exe$'):
            self.openFileInExistingVisualStudioInstance(filePath, lineNo)

            # This works too but doesn't allow going to a specific line
            #self._sys.executeNoWait('[VisualStudioCommandLinePath] /edit "{0}"'.format(filePath))
        else:
            # Unfortunately, in this case we can't pass in the line number
            self.openCustomSolution(project, platform, 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])))

    def openVisualStudioSolution(self, solutionPath, filePath = None):
        if not self._varMgr.hasKey('VisualStudioIdePath'):
            assertThat(False, "Path to visual studio has not been defined.  Please set <VisualStudioIdePath> within one of your {0} files", ConfigFileName)

        if self._sys.fileExists('[VisualStudioIdePath]'):
            self._sys.executeNoWait('"[VisualStudioIdePath]" {0} {1}'.format(self._sys.canonicalizePath(solutionPath), self._sys.canonicalizePath(filePath) if filePath else ""))
        else:
            assertThat(False, "Cannot find path to visual studio.  Expected to find it at '{0}'".format(self._varMgr.expand('[VisualStudioIdePath]')))

    def updateCustomSolution(self, project, platform):
        self._vsSolutionGenerator.updateVisualStudioSolution(project, platform)

    def openCustomSolution(self, project, platform, filePath = None):
        self.openVisualStudioSolution(self._getCustomSolutionPath(project, platform), filePath)

    def buildCustomSolution(self, project, platform):
        solutionPath = self._getCustomSolutionPath(project, platform)

        if not self._sys.fileExists(solutionPath):
            self._log.warn('Could not find generated custom solution.  Generating now.')
            self._vsSolutionGenerator.updateVisualStudioSolution(project, platform)

        self._log.heading('Building {0}-{1}.sln'.format(project, platform))
        self.buildVisualStudioProject(solutionPath, 'Debug')

    def buildVisualStudioProject(self, solutionPath, buildConfig):
        if self._config.getBool('Compilation', 'UseDevenv'):
            buildCommand = '"[VisualStudioCommandLinePath]" {0} /build "{1}"'.format(solutionPath, buildConfig)
        else:
            buildCommand = '"[MsBuildExePath]" /p:VisualStudioVersion=12.0'
            #if rebuild:
                #buildCommand += ' /t:Rebuild'
            buildCommand += ' /p:Configuration="{0}" "{1}"'.format(buildConfig, solutionPath)

        self._sys.executeAndWait(buildCommand)

    def _getCustomSolutionPath(self, project, platform):
        return '[UnityProjectsDir]/{0}/{0}-{1}.sln'.format(project, platform)

    def updateUnitySolution(self, projectName, platform):
        """
        Simply runs unity and then generates the monodevelop solution file using an editor script
        This is used when generating the Visual Studio Solution to get DLL references and defines etc.
        """
        self._log.heading('Updating unity generated solution for project {0} ({1})'.format(projectName, platform))

        self._packageManager.checkProjectInitialized(projectName, platform)

        # This will generate the unity csproj files which we need to generate Modest3d.sln correctly
        # It's also necessary to run this first on clean checkouts to initialize unity properly
        self._unityHelper.runEditorFunction(projectName, platform, 'Projeny.ProjenyEditorUtil.UpdateMonodevelopProject')
Пример #25
0
class VarManager:
    _config = Inject('Config')

    '''
    Stores a dictionary of keys to values to replace path variables with
    '''
    def __init__(self, initialParams = None):
        self._params = initialParams if initialParams else {}
        self._params['StartCurrentDir'] = os.getcwd()
        self._params['ExecDir'] = MiscUtil.getExecDirectory().replace('\\', '/')

        # We could just call self._config.getDictionary('PathVars') here but
        # then we wouldn't be able to use fallback (?) and override (!) characters in
        # our config

        self._regex = re.compile('^([^\[]*)(\[[^\]]*\])(.*)$')

    def hasKey(self, key):
        return key in self._params or self._config.tryGet('PathVars', key) != None

    def get(self, key):
        if key in self._params:
            return self._params[key]

        return self._config.getString('PathVars', key)

    def tryGet(self, key):
        if key in self._params:
            return self._params[key]

        return self._config.tryGetString(None, 'PathVars', key)

    def set(self, key, value):
        self._params[key] = value

    def expandPath(self, text, extraVars = None):
        ''' Same as expand() except it cleans up the path to remove ../ '''
        return os.path.realpath(self.expand(text, extraVars))

    def expand(self, text, extraVars = None):

        if not extraVars:
            extraVars = {}

        allArgs = self._params.copy()
        allArgs.update(extraVars)

        while True:
            match = self._regex.match(text)

            if not match:
                break

            prefix = match.group(1)
            var = match.group(2)
            suffix = match.group(3)

            var = var[1:-1]

            if var in allArgs:
                replacement = allArgs[var]
            else:
                replacement = self.get(var)

            text = prefix + replacement + suffix

        if '[' in text:
            raise Exception("Unable to find all keys in path '{0}'".format(text))

        return text
Пример #26
0
class Test2:
    title = Inject('Title', Assertions.IsInstanceOf(str))

    def Run(self):
        print('title: {0}'.format(self.title))
Пример #27
0
class Test1:
    con = Inject('Console', Assertions.HasMethods('WriteLine'))

    def Run(self):
        self.con.WriteLine('lorem ipsum')
Пример #28
0
class VisualStudioHelper:
    _log = Inject('Logger')
    _config = Inject('Config')
    _varMgr = Inject('VarManager')
    _sys = Inject('SystemHelper')

    def openFile(self, filePath, lineNo, solutionPath):
        if not lineNo or lineNo <= 0:
            lineNo = 1

        if MiscUtil.doesProcessExist('^devenv\.exe$'):
            self.openFileInExistingVisualStudioInstance(filePath, lineNo)

            # This works too but doesn't allow going to a specific line
            #self._sys.executeNoWait('[VisualStudioCommandLinePath] /edit "{0}"'.format(filePath))
        else:
            # Unfortunately, in this case we can't pass in the line number
            self.openVisualStudioSolution(solutionPath, filePath)

    def openFileInExistingVisualStudioInstance(self, filePath, lineNo):
        try:
            vsPath = self._varMgr.expand('[VisualStudioIdePath]')

            if '2017' in vsPath or 'Visual Studio 15.0' in vsPath:
                dte = win32com.client.GetActiveObject("VisualStudio.DTE.15.0")
            elif 'Visual Studio 14.0' in vsPath:
                dte = win32com.client.GetActiveObject("VisualStudio.DTE.14.0")
            elif 'Visual Studio 12.0' in vsPath:
                dte = win32com.client.GetActiveObject("VisualStudio.DTE.12.0")
            else:
                assertThat(False, "Could not determine visual studio version")

            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])))

    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 <VisualStudioIdePath> within one of your {0} files.  See documentation for details.",
                ConfigFileName)
            self._sys.executeShellCommand(solutionPath, None, False)

    def buildVisualStudioProject(self, solutionPath, buildConfig):
        solutionPath = self._varMgr.expand(solutionPath)
        if self._config.getBool('Compilation', 'UseDevenv'):
            buildCommand = '"[VisualStudioCommandLinePath]" {0} /build "{1}"'.format(
                solutionPath, buildConfig)
        else:
            buildCommand = '"[MsBuildExePath]" /p:VisualStudioVersion=12.0'
            #if rebuild:
            #buildCommand += ' /t:Rebuild'
            buildCommand += ' /p:Configuration="{0}" "{1}"'.format(
                buildConfig, solutionPath)

        self._sys.executeAndWait(buildCommand)
Пример #29
0
class Runner:
    _scriptRunner = Inject('ScriptRunner')
    _unityHelper = Inject('UnityHelper')
    _log = Inject('Logger')
    _sys = Inject('SystemHelper')
    _varManager = Inject('VarManager')

    def __init__(self):
        self._platform = Platforms.Windows

    def run(self, args):
        self._args = args
        success = self._scriptRunner.runWrapper(self._runInternal)

        if not success:
            sys.exit(1)

    def _runBuilds(self):

        if self._args.clearOutput:
            self._log.heading("Clearing output directory")
            self._sys.clearDirectoryContents('[OutputRootDir]')

        if self._args.buildType == 'all' or self._args.buildType == 'win35':
            self._log.heading("Building windows 3.5")
            self._platform = Platforms.Windows
            self._enableNet35()
            self._createBuild()

        if self._args.buildType == 'all' or self._args.buildType == 'win46':
            self._log.heading("Building windows 4.6")
            self._platform = Platforms.Windows
            self._enableNet46()
            self._createBuild()

        if self._args.buildType == 'all' or self._args.buildType == 'wsa35':
            self._log.heading("Building WindowsStoreApp 3.5 .net")
            self._platform = Platforms.WindowsStoreApp
            self._enableNet35()
            self._enableNetBackend()
            self._createBuild()

        if self._args.buildType == 'all' or self._args.buildType == 'wsa46':
            self._log.heading("Building WindowsStoreApp 4.6 .net")
            self._platform = Platforms.WindowsStoreApp
            self._enableNet46()
            self._enableNetBackend()
            self._createBuild()

        if self._args.buildType == 'all' or self._args.buildType == 'wsa46il2cpp':
            self._log.heading("Building WindowsStoreApp 4.6 il2cpp")
            self._platform = Platforms.WindowsStoreApp
            self._enableNet46()
            self._enableIl2cpp()
            self._createBuild()

        if self._args.buildType == 'all' or self._args.buildType == 'wsa35il2cpp':
            self._log.heading("Building WindowsStoreApp 3.5 il2cpp")
            self._platform = Platforms.WindowsStoreApp
            self._enableNet35()
            self._enableIl2cpp()
            self._createBuild()

        if self._args.buildType == 'all' or self._args.buildType == 'webgl35':
            self._log.heading("Building WebGl 3.5")
            self._platform = Platforms.WebGl
            self._enableNet35()
            self._createBuild()
            self._sys.copyFile('[WebGlTemplate]',
                               '[OutputRootDir]/WebGl/Net35/Web.config')

        if self._args.buildType == 'all' or self._args.buildType == 'webgl46':
            self._log.heading("Building WebGl 4.6")
            self._platform = Platforms.WebGl
            self._enableNet46()
            self._createBuild()
            self._sys.copyFile('[WebGlTemplate]',
                               '[OutputRootDir]/WebGl/Net46/Web.config')

        # TODO
        #self._log.heading("Building Ios")
        #self._platform = Platforms.Ios
        #self._createBuild()

        #self._log.heading("Building Android")
        #self._platform = Platforms.Android
        #self._createBuild()

    def _runTests(self):
        self._runUnityTests('editmode')
        self._runUnityTests('playmode')

    def _runUnityTests(self, testPlatform):

        self._log.heading('Running unity {0} unit tests'.format(testPlatform))

        resultPath = self._varManager.expandPath(
            '[TempDir]/UnityUnitTestsResults.xml').replace('\\', '/')
        self._sys.removeFileIfExists(resultPath)

        try:
            self._unityHelper.runEditorFunctionRaw(
                '[UnityProjectPath]', None, self._platform,
                '-runTests -batchmode -nographics -testResults "{0}" -testPlatform {1}'
                .format(resultPath, testPlatform))

        except UnityReturnedErrorCodeException as e:

            if self._sys.fileExists(resultPath):
                # Print out the test error info
                outRoot = ET.parse(resultPath)
                for item in outRoot.findall('.//failure/..'):
                    name = item.get('name')
                    self._log.error("Unit test failed for '{0}'.", name)

                    failure = item.find('./failure')
                    self._log.error("Message: {0}",
                                    failure.find('./message').text.strip())

                    stackTrace = failure.find('./stack-trace')

                    if stackTrace is not None:
                        self._log.error("Stack Trace: {0}",
                                        stackTrace.text.strip())
            raise

        outRoot = ET.parse(resultPath)
        total = outRoot.getroot().get('total')
        self._log.info("Processed {0} {1} tests without errors", total,
                       testPlatform)

    def _runInternal(self):

        if self._args.runTests:
            self._runTests()

        if self._args.runBuilds:
            self._runBuilds()

        if self._args.openUnity:
            self._openUnity()

    def _createBuild(self):
        self._log.info("Creating build")
        self._runEditorFunction('BuildRelease')
        #self._runEditorFunction('BuildDebug')

    def _enableNet46(self):
        self._log.info("Changing runtime to .net 4.6")
        self._runEditorFunction('EnableNet46')

    def _enableNet35(self):
        self._log.info("Changing runtime to .net 3.5")
        self._runEditorFunction('EnableNet35')

    def _enableNetBackend(self):
        self._log.info("Changing backend to .net")
        self._runEditorFunction('EnableBackendNet')

    def _enableIl2cpp(self):
        self._log.info("Enabling il2cpp")
        self._runEditorFunction('EnableBackendIl2cpp')

    def _openUnity(self):
        self._unityHelper.openUnity('[UnityProjectPath]', self._platform)

    def _runEditorFunction(self, functionName):
        self._log.info("Calling SampleBuilder." + functionName)
        self._unityHelper.runEditorFunction(
            '[UnityProjectPath]',
            'Zenject.Internal.SampleBuilder.' + functionName, self._platform)
Пример #30
0
class ProcessRunner:
    _log = Inject('Logger')

    def execNoWait(self, vals, startDir):
        params = {}

        if startDir != None:
            params['cwd'] = startDir

        Popen(vals, **params)

    def waitForProcessOrTimeout(self, commandVals, seconds, startDir = None):

        params = {}
        params['stdout'] = subprocess.PIPE
        params['stderr'] = subprocess.STDOUT

        if startDir != None:
            params['cwd'] = startDir

        proc = Popen(commandVals, **params)

        # TODO - clean this up so there's only one thread, then
        # do the timeout logic on the main thread
        timeout = KillProcessThread(seconds, proc.pid)
        timeout.run()

        def enqueueOutput(out, queue):
            for line in iter(out.readline, b''):
                queue.put(line)
            out.close()

        # We use a queue here instead of just calling stdout.readline() on the main thread
        # so that we can catch the KeyboardInterrupt event, and force kill the process
        queue = Queue()
        thread = threading.Thread(target = enqueueOutput, args = (proc.stdout, queue))
        thread.daemon = True # thread dies with the program
        thread.start()

        while True:
            try:
                try:
                    line = queue.get_nowait()
                    self._log.noise(line.decode(sys.stdout.encoding).rstrip())
                except Empty:
                    if not thread.isAlive():
                        break
                    time.sleep(0.2)
            except KeyboardInterrupt as e:
                self._log.error("Detected KeyboardInterrupt - killing process...")
                timeout.forceKill()
                raise e

        resultCode = proc.wait()

        timeout.cancel()

        if timeout.timeOutOccurred:
            return ResultType.TimedOut

        if resultCode != 0:
            return ResultType.Error

        return ResultType.Success

    # Note that in this case we pass the command as a string
    # This is recommended by the python docs here when using shell = True
    # https://docs.python.org/2/library/subprocess.html#subprocess.Popen
    def execShellCommand(self, commandStr, startDir = None, wait = True):

        params = {}
        params['stdout'] = subprocess.PIPE
        params['stderr'] = subprocess.PIPE
        params['shell'] = True

        if startDir != None:
            params['cwd'] = startDir

        # Would be nice to get back output in real time but I can't figure
        # out a way to do this
        # This method should only be used for a few command-prompt specific
        # commands anyway so not a big loss
        proc = Popen(commandStr, **params)

        if not wait:
            return ResultType.Success

        (stdoutData, stderrData) = proc.communicate()

        output = stdoutData.decode(encoding=sys.stdout.encoding, errors='ignore').strip()
        errors = stderrData.decode(encoding=sys.stderr.encoding, errors='ignore').strip()

        if output:
            for line in output.split('\n'):
                self._log.noise(line)

        if errors:
            self._log.error('Error occurred during command "{0}":'.format(commandStr))
            for line in errors.split('\n'):
                self._log.error('    ' + line)

        exitStatus = proc.returncode

        if exitStatus != 0:
            return ResultType.Error

        return ResultType.Success