Ejemplo n.º 1
0
    def _get_script_paths_from_folders_node(self) -> typing.Generator:
        """Returns script paths from the Folders element array"""
        for folder_node in filter(is_folder_node, self.folders_node):
            if folder_node.text == os.pardir:
                self.log.warning(
                    f'Folder paths cannot be equal to "{os.pardir}"')
                continue

            no_recurse: bool = folder_node.get('NoRecurse') == 'True'

            # try to add project path
            if folder_node.text == os.curdir:
                yield from PathHelper.find_script_paths_from_folder(
                    self.project_path, no_recurse)
                continue

            if startswith(folder_node.text,
                          self.remote_schemas,
                          ignorecase=True):
                local_path = self._get_remote_path(folder_node)
                PapyrusProject.log.info(
                    f'Adding import path from remote: "{local_path}"...')
                self.import_paths.insert(0, local_path)
                PapyrusProject.log.info(
                    f'Adding folder path from remote: "{local_path}"...')
                yield from PathHelper.find_script_paths_from_folder(
                    local_path, no_recurse)
                continue

            folder_path: str = os.path.normpath(folder_node.text)

            # try to add absolute path
            if os.path.isabs(folder_path) and os.path.isdir(folder_path):
                yield from PathHelper.find_script_paths_from_folder(
                    folder_path, no_recurse)
                continue

            # try to add project-relative folder path
            test_path = os.path.join(self.project_path, folder_path)
            if os.path.isdir(test_path):
                yield from PathHelper.find_script_paths_from_folder(
                    test_path, no_recurse)
                continue

            # try to add import-relative folder path
            for import_path in self.import_paths:
                test_path = os.path.join(import_path, folder_path)
                if os.path.isdir(test_path):
                    yield from PathHelper.find_script_paths_from_folder(
                        test_path, no_recurse)
Ejemplo n.º 2
0
    def build_commands(self) -> list:
        commands: list = []

        arguments: CommandArguments = CommandArguments()

        compiler_path: str = self.options.compiler_path
        flags_path: str = self.options.flags_path
        output_path: str = self.options.output_path
        import_paths: str = ';'.join(self.import_paths)

        if self.options.no_incremental_build:
            psc_paths = PathHelper.uniqify(self.psc_paths)
        else:
            psc_paths = self._try_exclude_unmodified_scripts()

        # add .psc scripts whose .pex counterparts do not exist
        for missing_psc_path in self.missing_scripts:
            if missing_psc_path not in psc_paths:
                psc_paths.append(missing_psc_path)

        # generate list of commands
        for psc_path in psc_paths:
            if self.options.game_type == 'fo4':
                psc_path = PathHelper.calculate_relative_object_name(
                    psc_path, self.import_paths)

            arguments.clear()
            arguments.append_quoted(compiler_path)
            arguments.append_quoted(psc_path)
            arguments.append_quoted(output_path, 'o')
            arguments.append_quoted(import_paths, 'i')
            arguments.append_quoted(flags_path, 'f')

            if self.options.game_type == 'fo4':
                # noinspection PyUnboundLocalVariable
                if self.release:
                    arguments.append('-release')

                # noinspection PyUnboundLocalVariable
                if self.final:
                    arguments.append('-final')

            if self.optimize:
                arguments.append('-op')

            commands.append(arguments.join())

        return commands
Ejemplo n.º 3
0
    def _find_modified_scripts(self) -> list:
        pex_paths: list = []

        for object_name, script_path in self.ppj.psc_paths.items():
            script_name, _ = os.path.splitext(os.path.basename(script_path))

            # if pex exists, compare time_t in pex header with psc's last modified timestamp
            pex_match: list = [pex_path for pex_path in self.ppj.pex_paths
                               if endswith(pex_path, f'{script_name}.pex', ignorecase=True)]
            if not pex_match:
                continue

            pex_path: str = pex_match[0]
            if not os.path.isfile(pex_path):
                continue

            try:
                header = PexReader.get_header(pex_path)
            except ValueError:
                BuildFacade.log.error(f'Cannot determine compilation time due to unknown magic: "{pex_path}"')
                sys.exit(1)

            psc_last_modified: float = os.path.getmtime(script_path)
            pex_last_compiled: float = float(header.compilation_time.value)

            # if psc is older than the pex
            if psc_last_modified < pex_last_compiled:
                pex_paths.append(pex_path)

        return PathHelper.uniqify(pex_paths)
Ejemplo n.º 4
0
    def _find_modified_scripts(self) -> list:
        pex_paths: list = []

        for psc_path in self.ppj.psc_paths:
            script_name, _ = os.path.splitext(os.path.basename(psc_path))

            # if pex exists, compare time_t in pex header with psc's last modified timestamp
            pex_match: list = [
                pex_path for pex_path in self.ppj.pex_paths
                if pex_path.endswith('%s.pex' % script_name)
            ]
            if not pex_match:
                continue

            pex_path: str = pex_match[0]
            if not os.path.exists(pex_path):
                continue

            try:
                header = PexReader.get_header(pex_path)
            except ValueError:
                BuildFacade.log.warning(
                    'Cannot determine compilation time from compiled script due to unknown file magic: "%s"'
                    % pex_path)
                continue

            compiled_time: int = header.compilation_time.value

            # if psc is older than the pex
            if os.path.getmtime(psc_path) >= compiled_time:
                pex_paths.append(pex_path)

        return PathHelper.uniqify(pex_paths)
Ejemplo n.º 5
0
    def _try_exclude_unmodified_scripts(self) -> list:
        psc_paths: list = []

        for psc_path in self.psc_paths:
            script_name, _ = os.path.splitext(os.path.basename(psc_path))

            # if pex exists, compare time_t in pex header with psc's last modified timestamp
            matching_path: str = ''
            for pex_path in self.pex_paths:
                if pex_path.endswith('%s.pex' % script_name):
                    matching_path = pex_path
                    break

            if not os.path.exists(matching_path):
                continue

            try:
                header = PexReader.get_header(matching_path)
            except ValueError:
                PapyrusProject.log.warning(
                    'Cannot determine compilation time from compiled script due to unknown file magic: "%s"'
                    % matching_path)
                continue

            compiled_time: int = header.compilation_time.value
            if os.path.getmtime(psc_path) < compiled_time:
                continue

            psc_paths.append(psc_path)

        return PathHelper.uniqify(psc_paths)
Ejemplo n.º 6
0
    def _get_implicit_folder_imports(self) -> list:
        """Returns absolute implicit import paths from Folder node paths"""
        implicit_paths: list = []

        folder_nodes = ElementHelper.get(self.root_node, 'Folders')
        if folder_nodes is None:
            return []

        for folder_node in folder_nodes:
            if not folder_node.tag.endswith('Folder'):
                continue

            if not folder_node.text:
                continue

            folder_path: str = os.path.normpath(self.parse(folder_node.text))

            if os.path.isabs(folder_path):
                if os.path.exists(folder_path):
                    implicit_paths.append(folder_path)
            else:
                test_path = os.path.join(self.project_path, folder_path)
                if os.path.exists(test_path):
                    implicit_paths.append(test_path)

        return PathHelper.uniqify(implicit_paths)
Ejemplo n.º 7
0
    def _get_import_paths(self) -> list:
        """Returns absolute import paths from Papyrus Project"""
        results: list = []

        import_nodes: etree.ElementBase = ElementHelper.get(
            self.root_node, 'Imports')
        if import_nodes is None:
            return []

        for import_node in import_nodes:
            if not import_node.tag.endswith('Import'):
                continue

            if not import_node.text:
                continue

            import_path: str = os.path.normpath(self.parse(import_node.text))

            if import_path == os.pardir:
                self.log.warning('Import paths cannot be equal to ".."')
                continue

            if import_path == os.curdir:
                import_path = self.project_path
            elif not os.path.isabs(import_path):
                # relative import paths should be relative to the project
                import_path = os.path.normpath(
                    os.path.join(self.project_path, import_path))

            if os.path.exists(import_path):
                results.append(import_path)

        return PathHelper.uniqify(results)
Ejemplo n.º 8
0
    def _get_script_paths_from_folders_node(self) -> list:
        """Returns script paths from the Folders element array"""
        folder_nodes = ElementHelper.get(self.root_node, 'Folders')
        if folder_nodes is None:
            return []

        script_paths: list = []
        folder_paths: list = []

        for folder_node in folder_nodes:
            if not folder_node.tag.endswith('Folder'):
                continue

            folder_text: str = self.parse(folder_node.text)

            if folder_text == os.pardir:
                self.log.warning('Folder paths cannot be equal to ".."')
                continue

            no_recurse: bool = self._get_attr_as_bool(folder_node, 'NoRecurse')

            # try to add project path
            if folder_text == os.curdir:
                folder_paths.append((self.project_path, no_recurse))
                continue

            folder_path: str = os.path.normpath(folder_text)

            # try to add absolute path
            if os.path.isabs(folder_path) and os.path.isdir(folder_path):
                folder_paths.append((folder_path, no_recurse))
                continue

            # try to add project-relative folder path
            test_path = os.path.join(self.project_path, folder_path)
            if os.path.isdir(test_path):
                folder_paths.append((test_path, no_recurse))
                continue

            # try to add import-relative folder path
            for import_path in self.import_paths:
                test_path = os.path.join(import_path, folder_path)
                if os.path.isdir(test_path):
                    folder_paths.append((test_path, no_recurse))
                    continue

            PapyrusProject.log.warning('Cannot resolve folder path: "%s"' %
                                       folder_node.text)

        for folder_path, no_recurse in folder_paths:
            search_path: str = os.path.join(folder_path,
                                            '*' if no_recurse else '**\*')
            script_paths.extend([
                f for f in glob.iglob(search_path, recursive=not no_recurse)
                if os.path.isfile(f) and f.casefold().endswith('.psc')
            ])

        return PathHelper.uniqify(script_paths)
Ejemplo n.º 9
0
    def _find_missing_script_paths(self) -> list:
        """Returns list of script paths for compiled scripts that do not exist"""
        results: list = []

        for psc_path in self.psc_paths:
            if self.options.game_type == 'fo4':
                object_name = PathHelper.calculate_relative_object_name(
                    psc_path, self.import_paths)
            else:
                object_name = os.path.basename(psc_path)

            pex_path: str = os.path.join(self.options.output_path,
                                         object_name.replace('.psc', '.pex'))
            if os.path.exists(pex_path):
                continue

            results.append(psc_path)

        return PathHelper.uniqify(results)
Ejemplo n.º 10
0
    def _get_pex_paths(self) -> list:
        """
        Returns absolute paths to compiled scripts that may not exist yet in output folder
        """
        pex_paths: list = []

        for psc_path in self.psc_paths:
            if self.options.game_type == 'fo4':
                object_name = PathHelper.calculate_relative_object_name(
                    psc_path, self.import_paths)
            else:
                object_name = os.path.basename(psc_path)

            pex_path = os.path.join(self.options.output_path,
                                    object_name.replace('.psc', '.pex'))

            pex_paths.append(pex_path)

        return PathHelper.uniqify(pex_paths)
Ejemplo n.º 11
0
    def try_populate_imports(self) -> None:
        # we need to populate the list of import paths before we try to determine the game type
        # because the game type can be determined from import paths
        self.import_paths = self._get_import_paths()
        if not self.import_paths:
            PapyrusProject.log.error('Failed to build list of import paths')
            sys.exit(1)

        if not self.options.no_implicit_imports:
            # ensure that folder paths are implicitly imported
            implicit_folder_paths: list = self._get_implicit_folder_imports()

            if len(implicit_folder_paths) > 0:
                PapyrusProject.log.info(
                    'Implicitly imported folder paths found:')
                for path in implicit_folder_paths:
                    PapyrusProject.log.info(f'+ "{path}"')

                PathHelper.merge_implicit_import_paths(implicit_folder_paths,
                                                       self.import_paths)

        # we need to populate psc paths after explicit and implicit import paths are populated
        # this also needs to be set before we populate implicit import paths from psc paths
        # not sure if this must run again after populating implicit import paths from psc paths
        self.psc_paths = self._get_psc_paths()
        if not self.psc_paths:
            PapyrusProject.log.error('Failed to build list of script paths')
            sys.exit(1)

        if not self.options.no_implicit_imports:
            # this adds implicit imports from script paths
            implicit_script_paths: list = self._get_implicit_script_imports()

            if len(implicit_script_paths) > 0:
                PapyrusProject.log.info(
                    'Implicitly imported script paths found:')
                for path in implicit_script_paths:
                    PapyrusProject.log.info(f'+ "{path}"')

                PathHelper.merge_implicit_import_paths(implicit_script_paths,
                                                       self.import_paths)
Ejemplo n.º 12
0
    def _get_implicit_script_imports(self) -> list:
        """Returns absolute implicit import paths from Script node paths"""
        implicit_paths: list = []

        for psc_path in self.psc_paths:
            for import_path in self.import_paths:
                relpath = os.path.relpath(os.path.dirname(psc_path),
                                          import_path)
                test_path = os.path.normpath(os.path.join(
                    import_path, relpath))
                if os.path.exists(test_path):
                    implicit_paths.append(test_path)

        return PathHelper.uniqify(implicit_paths)
Ejemplo n.º 13
0
    def _get_psc_paths(self) -> list:
        """Returns script paths from Folders and Scripts nodes"""
        paths: list = []

        # try to populate paths with scripts from Folders and Scripts nodes
        for tag in ('Folders', 'Scripts'):
            node = ElementHelper.get(self.root_node, tag)
            if node is None:
                continue
            node_paths = getattr(
                self, '_get_script_paths_from_%s_node' % tag.casefold())()
            PapyrusProject.log.info(
                '%s script paths discovered from %s nodes.' %
                (len(node_paths), tag[:-1]))
            if node_paths:
                paths.extend(node_paths)

        results: list = []

        # convert user paths to absolute paths
        for path in paths:
            # try to add existing absolute paths
            if os.path.isabs(path) and os.path.exists(path):
                results.append(path)
                continue

            # try to add existing project-relative paths
            test_path = os.path.join(self.project_path, path)
            if os.path.exists(test_path):
                results.append(test_path)
                continue

            # try to add existing import-relative paths
            for import_path in self.import_paths:
                if not os.path.isabs(import_path):
                    import_path = os.path.join(self.project_path, import_path)

                test_path = os.path.join(import_path, path)
                if os.path.exists(test_path):
                    results.append(test_path)
                    break

        results = PathHelper.uniqify(results)

        PapyrusProject.log.info(
            '%s unique script paths resolved to absolute paths.' %
            len(results))

        return results
Ejemplo n.º 14
0
    def _get_implicit_folder_imports(self) -> list:
        """Returns absolute implicit import paths from Folder node paths"""
        implicit_paths: list = []

        if self.folders_node is None:
            return []

        try_append_path = lambda path: implicit_paths.append(path) \
            if os.path.isdir(path) and path not in self.import_paths else None

        for folder_node in filter(is_folder_node, self.folders_node):
            folder_path: str = os.path.normpath(folder_node.text)
            try_append_path(folder_path if os.path.isabs(folder_path) else os.
                            path.join(self.project_path, folder_path))

        return PathHelper.uniqify(implicit_paths)
Ejemplo n.º 15
0
    def _try_fix_input_path(self, input_path: str) -> str:
        if not input_path:
            Application.log.error('required argument missing: -i INPUT.ppj')
            self._print_help_and_exit()

        if startswith(input_path, 'file:', ignorecase=True):
            full_path = PathHelper.url2pathname(input_path)
            input_path = os.path.normpath(full_path)

        if not os.path.isabs(input_path):
            cwd = os.getcwd()
            Application.log.warning(f'Using working directory: "{cwd}"')

            input_path = os.path.join(cwd, input_path)

        Application.log.warning(f'Using input path: "{input_path}"')

        return input_path
Ejemplo n.º 16
0
    def _get_script_paths_from_scripts_node(self) -> list:
        """Returns script paths from the Scripts node"""
        paths: list = []

        script_nodes = ElementHelper.get(self.root_node, 'Scripts')
        if script_nodes is None:
            return []

        for script_node in script_nodes:
            if not script_node.tag.endswith('Script'):
                continue

            psc_path: str = self.parse(script_node.text)

            if ':' in psc_path:
                psc_path = psc_path.replace(':', os.sep)

            paths.append(os.path.normpath(psc_path))

        return PathHelper.uniqify(paths)
Ejemplo n.º 17
0
    def _get_import_paths(self) -> list:
        """Returns absolute import paths from Papyrus Project"""
        results: list = []

        if self.imports_node is None:
            return []

        for import_node in filter(is_import_node, self.imports_node):
            import_path: str = import_node.text

            if startswith(import_path, self.remote_schemas, ignorecase=True):
                local_path = self._get_remote_path(import_node)
                PapyrusProject.log.info(
                    f'Adding import path from remote: "{local_path}"...')
                results.append(local_path)
                continue

            if import_path == os.pardir or startswith(import_path, os.pardir):
                import_path = import_path.replace(
                    os.pardir,
                    os.path.normpath(os.path.join(self.project_path,
                                                  os.pardir)), 1)
            elif import_path == os.curdir or startswith(
                    import_path, os.curdir):
                import_path = import_path.replace(os.curdir, self.project_path,
                                                  1)

            # relative import paths should be relative to the project
            if not os.path.isabs(import_path):
                import_path = os.path.join(self.project_path, import_path)

            import_path = os.path.normpath(import_path)

            if os.path.isdir(import_path):
                results.append(import_path)
            else:
                PapyrusProject.log.error(
                    f'Import path does not exist: "{import_path}"')
                sys.exit(1)

        return PathHelper.uniqify(results)
Ejemplo n.º 18
0
    def _get_implicit_folder_imports(self) -> list:
        """Returns absolute implicit import paths from Folder node paths"""
        implicit_paths: list = []

        if not self.has_folders_node:
            return []

        for folder_node in filter(is_folder_node, self.folders_node):
            folder_path: str = os.path.normpath(folder_node.text)

            if os.path.isabs(folder_path):
                if os.path.isdir(
                        folder_path) and folder_path not in self.import_paths:
                    implicit_paths.append(folder_path)
            else:
                test_path = os.path.join(self.project_path, folder_path)
                if os.path.isdir(
                        test_path) and test_path not in self.import_paths:
                    implicit_paths.append(test_path)

        return PathHelper.uniqify(implicit_paths)
Ejemplo n.º 19
0
    def _get_import_paths(self) -> list:
        """Returns absolute import paths from Papyrus Project"""
        results: list = []

        if not self.has_imports_node:
            return []

        for import_node in filter(is_import_node, self.imports_node):
            if startswith(import_node.text,
                          self.remote_schemas,
                          ignorecase=True):
                local_path = self._get_remote_path(import_node)
                PapyrusProject.log.info(
                    f'Adding import path from remote: "{local_path}"...')
                results.append(local_path)
                continue

            import_path = os.path.normpath(import_node.text)

            if import_path == os.pardir:
                self.log.warning(
                    f'Import paths cannot be equal to "{os.pardir}"')
                continue

            if import_path == os.curdir:
                import_path = self.project_path
            elif not os.path.isabs(import_path):
                # relative import paths should be relative to the project
                import_path = os.path.normpath(
                    os.path.join(self.project_path, import_path))

            if os.path.isdir(import_path):
                results.append(import_path)
            else:
                self.log.error(f'Import path does not exist: "{import_path}"')
                sys.exit(1)

        return PathHelper.uniqify(results)
Ejemplo n.º 20
0
    def _get_implicit_script_imports(self) -> list:
        """Returns absolute implicit import paths from Script node paths"""
        implicit_paths: list = []

        for object_name, script_path in self.psc_paths.items():
            script_folder_path = os.path.dirname(script_path)

            for import_path in self.import_paths:
                # TODO: figure out how to handle imports on different drives
                try:
                    relpath = os.path.relpath(script_folder_path, import_path)
                except ValueError as e:
                    PapyrusProject.log.warning(
                        f'{e} (path: "{script_folder_path}", start: "{import_path}")'
                    )
                    continue

                test_path = os.path.normpath(os.path.join(
                    import_path, relpath))
                if os.path.isdir(
                        test_path) and test_path not in self.import_paths:
                    implicit_paths.append(test_path)

        return PathHelper.uniqify(implicit_paths)
Ejemplo n.º 21
0
    def __init__(self, options: ProjectOptions) -> None:
        super().__init__(options)

        xml_parser: etree.XMLParser = etree.XMLParser(remove_blank_text=True,
                                                      remove_comments=True)

        # strip comments from raw text because lxml.etree.XMLParser does not remove XML-unsupported comments
        # e.g., '<PapyrusProject <!-- xmlns="PapyrusProject.xsd" -->>'
        xml_document: io.StringIO = PapyrusProject._strip_xml_comments(
            self.options.input_path)

        project_xml: etree.ElementTree = etree.parse(xml_document, xml_parser)
        self.root_node: etree.ElementBase = project_xml.getroot()

        # TODO: validate earlier
        schema: etree.XMLSchema = ElementHelper.validate_schema(
            self.root_node, self.program_path)
        if schema:
            try:
                if schema.assertValid(project_xml) is None:
                    PapyrusProject.log.info(
                        'Successfully validated XML Schema.')
            except etree.DocumentInvalid as e:
                PapyrusProject.log.error(
                    'Failed to validate XML Schema.%s\t%s' % (os.linesep, e))
                sys.exit(1)

        variables_node = ElementHelper.get(self.root_node, 'Variables')
        if variables_node is not None:
            for variable_node in variables_node:
                if not variable_node.tag.endswith('Variable'):
                    continue

                var_key = variable_node.get('Name', default='')
                var_value = variable_node.get('Value', default='')

                if any([not var_key, not var_value]):
                    continue

                self.variables.update({var_key: var_value})

        # we need to parse all PapyrusProject attributes after validating and before we do anything else
        # options can be overridden by arguments when the BuildFacade is initialized
        self.options.output_path = self.parse(
            self.root_node.get('Output', default=self.options.output_path))
        self.options.flags_path = self.parse(
            self.root_node.get('Flags', default=self.options.flags_path))

        self.optimize: bool = PapyrusProject._get_attr_as_bool(
            self.root_node, 'Optimize')
        self.release: bool = PapyrusProject._get_attr_as_bool(
            self.root_node, 'Release')
        self.final: bool = PapyrusProject._get_attr_as_bool(
            self.root_node, 'Final')

        self.options.anonymize = PapyrusProject._get_attr_as_bool(
            self.root_node, 'Anonymize')
        self.options.bsarch = PapyrusProject._get_attr_as_bool(
            self.root_node, 'Package')
        self.options.zip = PapyrusProject._get_attr_as_bool(
            self.root_node, 'Zip')

        self.packages_node = ElementHelper.get(self.root_node, 'Packages')

        if self.options.bsarch:
            if self.packages_node is None:
                PapyrusProject.log.error(
                    'Package is enabled but the Packages node is undefined. Setting Package to false.'
                )
                self.options.bsarch = False
            else:
                self.options.package_path = self.parse(
                    self.packages_node.get('Output',
                                           default=self.options.package_path))

        self.zipfile_node = ElementHelper.get(self.root_node, 'ZipFile')

        if self.options.zip:
            if self.zipfile_node is None:
                PapyrusProject.log.error(
                    'Zip is enabled but the ZipFile node is undefined. Setting Zip to false.'
                )
                self.options.zip = False
            else:
                self._setup_zipfile_options()

        # we need to populate the list of import paths before we try to determine the game type
        # because the game type can be determined from import paths
        self.import_paths: list = self._get_import_paths()
        if not self.import_paths:
            PapyrusProject.log.error('Failed to build list of import paths')
            sys.exit(1)

        # ensure that folder paths are implicitly imported
        implicit_folder_paths: list = self._get_implicit_folder_imports()
        PathHelper.merge_implicit_import_paths(implicit_folder_paths,
                                               self.import_paths)

        for path in implicit_folder_paths:
            if path in self.import_paths:
                PapyrusProject.log.warning(
                    'Using import path implicitly: "%s"' % path)

        self.psc_paths: list = self._get_psc_paths()
        if not self.psc_paths:
            PapyrusProject.log.error('Failed to build list of script paths')
            sys.exit(1)

        # this adds implicit imports from script paths
        implicit_script_paths: list = self._get_implicit_script_imports()
        PathHelper.merge_implicit_import_paths(implicit_script_paths,
                                               self.import_paths)

        for path in implicit_script_paths:
            if path in self.import_paths:
                PapyrusProject.log.warning(
                    'Using import path implicitly: "%s"' % path)

        # we need to set the game type after imports are populated but before pex paths are populated
        # allow xml to set game type but defer to passed argument
        if not self.options.game_type:
            game_type: str = self.parse(self.root_node.get(
                'Game', default='')).casefold()

            if game_type and game_type in self.game_types:
                PapyrusProject.log.warning(
                    'Using game type: %s (determined from Papyrus Project)' %
                    self.game_types[game_type])
                self.options.game_type = game_type

        if not self.options.game_type:
            self.options.game_type = self.get_game_type()

        if not self.options.game_type:
            PapyrusProject.log.error(
                'Cannot determine game type from arguments or Papyrus Project')
            sys.exit(1)

        # get expected pex paths - these paths may not exist and that is okay!
        self.pex_paths: list = self._get_pex_paths()

        # these are relative paths to psc scripts whose pex counterparts are missing
        self.missing_scripts: list = self._find_missing_script_paths()

        # game type must be set before we call this
        if not self.options.game_path:
            self.options.game_path = self.get_game_path()
Ejemplo n.º 22
0
    def __init__(self, options: ProjectOptions) -> None:
        super(PapyrusProject, self).__init__(options)

        xml_parser: etree.XMLParser = etree.XMLParser(remove_blank_text=True,
                                                      remove_comments=True)

        # strip comments from raw text because lxml.etree.XMLParser does not remove XML-unsupported comments
        # e.g., '<PapyrusProject <!-- xmlns="PapyrusProject.xsd" -->>'
        xml_document: io.StringIO = XmlHelper.strip_xml_comments(
            self.options.input_path)

        project_xml: etree.ElementTree = etree.parse(xml_document, xml_parser)

        self.ppj_root = XmlRoot(project_xml)

        schema: etree.XMLSchema = XmlHelper.validate_schema(
            self.ppj_root.ns, self.program_path)

        if schema:
            try:
                schema.assertValid(project_xml)
            except etree.DocumentInvalid as e:
                PapyrusProject.log.error(
                    f'Failed to validate XML Schema.{os.linesep}\t{e}')
                sys.exit(1)
            else:
                PapyrusProject.log.info('Successfully validated XML Schema.')

        # variables need to be parsed before nodes are updated
        variables_node = self.ppj_root.find('Variables')
        if variables_node is not None:
            self._parse_variables(variables_node)

        # we need to parse all attributes after validating and before we do anything else
        # options can be overridden by arguments when the BuildFacade is initialized
        self._update_attributes(self.ppj_root.node)

        if self.options.resolve_ppj:
            xml_output = etree.tostring(self.ppj_root.node,
                                        encoding='utf-8',
                                        xml_declaration=True,
                                        pretty_print=True)
            PapyrusProject.log.debug(
                f'Resolved PPJ. Text output:{os.linesep * 2}{xml_output.decode()}'
            )
            sys.exit(1)

        self.options.flags_path = self.ppj_root.get('Flags')
        self.options.output_path = self.ppj_root.get('Output')

        self.optimize = self.ppj_root.get('Optimize') == 'True'
        self.release = self.ppj_root.get('Release') == 'True'
        self.final = self.ppj_root.get('Final') == 'True'

        self.options.anonymize = self.ppj_root.get('Anonymize') == 'True'
        self.options.package = self.ppj_root.get('Package') == 'True'
        self.options.zip = self.ppj_root.get('Zip') == 'True'

        self.imports_node = self.ppj_root.find('Imports')
        self.has_imports_node = self.imports_node is not None

        self.scripts_node = self.ppj_root.find('Scripts')
        self.has_scripts_node = self.scripts_node is not None

        self.folders_node = self.ppj_root.find('Folders')
        self.has_folders_node = self.folders_node is not None

        self.packages_node = self.ppj_root.find('Packages')
        self.has_packages_node = self.packages_node is not None

        self.zip_files_node = self.ppj_root.find('ZipFiles')
        self.has_zip_files_node = self.zip_files_node is not None

        self.pre_build_node = self.ppj_root.find('PreBuildEvent')
        self.has_pre_build_node = self.pre_build_node is not None

        self.post_build_node = self.ppj_root.find('PostBuildEvent')
        self.has_post_build_node = self.post_build_node is not None

        if self.options.package and self.has_packages_node:
            if not self.options.package_path:
                self.options.package_path = self.packages_node.get('Output')

        if self.options.zip and self.has_zip_files_node:
            if not self.options.zip_output_path:
                self.options.zip_output_path = self.zip_files_node.get(
                    'Output')

        # initialize remote if needed
        if self.remote_paths:
            if not self.options.remote_temp_path:
                self.options.remote_temp_path = self.get_remote_temp_path()

            self.remote = GenericRemote(self.options)

            # validate remote paths
            for path in self.remote_paths:
                if not self.remote.validate_url(path):
                    PapyrusProject.log.error(
                        f'Cannot proceed while node contains invalid URL: "{path}"'
                    )
                    sys.exit(1)

        # we need to populate the list of import paths before we try to determine the game type
        # because the game type can be determined from import paths
        self.import_paths = self._get_import_paths()
        if not self.import_paths:
            PapyrusProject.log.error('Failed to build list of import paths')
            sys.exit(1)

        # ensure that folder paths are implicitly imported
        implicit_folder_paths: list = self._get_implicit_folder_imports()

        if len(implicit_folder_paths) > 0:
            PapyrusProject.log.info('Implicitly imported folder paths found:')
            for path in implicit_folder_paths:
                PapyrusProject.log.info(f'+ "{path}"')

            PathHelper.merge_implicit_import_paths(implicit_folder_paths,
                                                   self.import_paths)

        # we need to populate psc paths after explicit and implicit import paths are populated
        # this also needs to be set before we populate implicit import paths from psc paths
        # not sure if this must run again after populating implicit import paths from psc paths
        self.psc_paths = self._get_psc_paths()
        if not self.psc_paths:
            PapyrusProject.log.error('Failed to build list of script paths')
            sys.exit(1)

        # this adds implicit imports from script paths
        implicit_script_paths: list = self._get_implicit_script_imports()

        if len(implicit_script_paths) > 0:
            PapyrusProject.log.info('Implicitly imported script paths found:')
            for path in implicit_script_paths:
                PapyrusProject.log.info(f'+ "{path}"')

            PathHelper.merge_implicit_import_paths(implicit_script_paths,
                                                   self.import_paths)

        # we need to set the game type after imports are populated but before pex paths are populated
        # allow xml to set game type but defer to passed argument
        if not self.options.game_type:
            game_type: str = self.ppj_root.get('Game', default='').upper()

            if game_type and GameType.has_member(game_type):
                valid_game_type: GameType = GameType[game_type]
                PapyrusProject.log.warning(
                    f'Using game type: {self.game_names[valid_game_type]} (determined from Papyrus Project)'
                )
                self.options.game_type = valid_game_type

        if not self.options.game_type:
            self.options.game_type = self.get_game_type()

        if not self.options.game_type:
            PapyrusProject.log.error(
                'Cannot determine game type from arguments or Papyrus Project')
            sys.exit(1)

        # get expected pex paths - these paths may not exist and that is okay!
        self.pex_paths = self._get_pex_paths()

        # these are relative paths to psc scripts whose pex counterparts are missing
        self.missing_scripts: dict = self._find_missing_script_paths()

        # game type must be set before we call this
        if not self.options.game_path:
            self.options.game_path = self.get_game_path(self.options.game_type)
Ejemplo n.º 23
0
 def _calculate_object_name(self, psc_path: str) -> str:
     return PathHelper.calculate_relative_object_name(
         psc_path, self.import_paths)
Ejemplo n.º 24
0
    def _get_script_paths_from_folders_node(self) -> typing.Generator:
        """Returns script paths from the Folders element array"""
        for folder_node in filter(is_folder_node, self.folders_node):
            self.try_fix_namespace_path(folder_node)

            attr_no_recurse: bool = folder_node.get(
                XmlAttributeName.NO_RECURSE) == 'True'

            folder_path: str = folder_node.text

            # handle . and .. in path
            if folder_path == os.pardir or startswith(folder_path, os.pardir):
                folder_path = folder_path.replace(
                    os.pardir,
                    os.path.normpath(os.path.join(self.project_path,
                                                  os.pardir)), 1)
                yield from PathHelper.find_script_paths_from_folder(
                    folder_path, no_recurse=attr_no_recurse)
                continue

            if folder_path == os.curdir or startswith(folder_path, os.curdir):
                folder_path = folder_path.replace(os.curdir, self.project_path,
                                                  1)
                yield from PathHelper.find_script_paths_from_folder(
                    folder_path, no_recurse=attr_no_recurse)
                continue

            if startswith(folder_path, self.remote_schemas, ignorecase=True):
                local_path = self._get_remote_path(folder_node)
                PapyrusProject.log.info(
                    f'Adding import path from remote: "{local_path}"...')
                self.import_paths.insert(0, local_path)
                PapyrusProject.log.info(
                    f'Adding folder path from remote: "{local_path}"...')
                yield from PathHelper.find_script_paths_from_folder(
                    local_path, no_recurse=attr_no_recurse)
                continue

            folder_path = os.path.normpath(folder_path)

            # try to add absolute path
            if os.path.isabs(folder_path) and os.path.isdir(folder_path):
                yield from PathHelper.find_script_paths_from_folder(
                    folder_path, no_recurse=attr_no_recurse)
                continue

            # try to add project-relative folder path
            test_path = os.path.join(self.project_path, folder_path)
            if os.path.isdir(test_path):
                # count scripts to avoid issue where an errant `test_path` may exist and contain no sources
                # this can be a problem if that folder contains sources but user error is hard to fix
                test_passed = False

                user_flags = wcmatch.RECURSIVE if not attr_no_recurse else 0x0
                matcher = wcmatch.WcMatch(test_path,
                                          '*.psc',
                                          flags=wcmatch.IGNORECASE
                                          | user_flags)
                for _ in matcher.imatch():
                    test_passed = True
                    break

                if test_passed:
                    yield from PathHelper.find_script_paths_from_folder(
                        test_path, no_recurse=attr_no_recurse, matcher=matcher)
                    continue

            # try to add import-relative folder path
            for import_path in self.import_paths:
                test_path = os.path.join(import_path, folder_path)
                if os.path.isdir(test_path):
                    yield from PathHelper.find_script_paths_from_folder(
                        test_path, no_recurse=attr_no_recurse)
Ejemplo n.º 25
0
    def _populate_include_paths(self, parent_node: etree.ElementBase,
                                root_path: str) -> list:
        include_paths: list = []

        for include_node in parent_node:
            if not include_node.tag.endswith('Include'):
                continue

            no_recurse: bool = self.ppj._get_attr_as_bool(
                include_node, 'NoRecurse')
            wildcard_pattern: str = '*' if no_recurse else '**\*'

            include_text: str = self.ppj.parse(include_node.text)

            if include_text == os.curdir or include_text == os.pardir:
                PackageManager.log.warning(
                    'Include paths cannot be equal to "." or ".."')
                continue

            if include_text.startswith('.'):
                PackageManager.log.warning(
                    'Include paths cannot start with "."')
                continue

            # populate files list using simple glob patterns
            if '*' in include_text:
                search_path: str = os.path.join(root_path, wildcard_pattern)
                files: list = [
                    f
                    for f in glob.iglob(search_path, recursive=not no_recurse)
                    if os.path.isfile(f)
                ]
                matches: list = fnmatch.filter(files, include_text)
                if not matches:
                    PackageManager.log.warning(
                        'No files in "%s" matched glob pattern: %s' %
                        (search_path, include_text))
                include_paths.extend(matches)
                continue

            include_path: str = os.path.normpath(include_text)

            # populate files list using absolute paths
            if os.path.isabs(include_path) and os.path.exists(include_path):
                if root_path not in include_path:
                    PackageManager.log.warning(
                        'Cannot include path outside RootDir: "%s"' %
                        include_path)
                    continue
                include_paths.append(include_path)
                continue

            # populate files list using relative file path
            test_path = os.path.join(root_path, include_path)
            if not os.path.isdir(test_path):
                include_paths.append(test_path)
                continue

            # populate files list using relative folder path
            search_path = os.path.join(root_path, include_path,
                                       wildcard_pattern)
            include_paths.extend([
                f for f in glob.iglob(search_path, recursive=not no_recurse)
                if os.path.isfile(f)
            ])

        return PathHelper.uniqify(include_paths)
Ejemplo n.º 26
0
    def _generate_include_paths(includes_node: etree.ElementBase,
                                root_path: str) -> typing.Generator:
        for include_node in filter(is_include_node, includes_node):
            no_recurse: bool = include_node.get('NoRecurse') == 'True'
            wildcard_pattern: str = '*' if no_recurse else r'**\*'

            if include_node.text.startswith(os.pardir):
                PackageManager.log.warning(
                    f'Include paths cannot start with "{os.pardir}"')
                continue

            if include_node.text == os.curdir or include_node.text.startswith(
                    os.curdir):
                include_node.text = include_node.text.replace(
                    os.curdir, root_path, 1)

            # normalize path
            path_or_pattern = os.path.normpath(include_node.text)

            # populate files list using simple glob patterns
            if '*' in path_or_pattern:
                if not os.path.isabs(path_or_pattern):
                    search_path = os.path.join(root_path, wildcard_pattern)
                elif root_path in path_or_pattern:
                    search_path = path_or_pattern
                else:
                    PackageManager.log.warning(
                        f'Cannot include path outside RootDir: "{path_or_pattern}"'
                    )
                    continue

                for include_path in glob.iglob(search_path,
                                               recursive=not no_recurse):
                    if os.path.isfile(include_path) and fnmatch.fnmatch(
                            include_path, path_or_pattern):
                        yield include_path

            # populate files list using absolute paths
            elif os.path.isabs(path_or_pattern):
                if root_path not in path_or_pattern:
                    PackageManager.log.warning(
                        f'Cannot include path outside RootDir: "{path_or_pattern}"'
                    )
                    continue

                if os.path.isfile(path_or_pattern):
                    yield path_or_pattern
                else:
                    search_path = os.path.join(path_or_pattern,
                                               wildcard_pattern)
                    yield from PathHelper.find_include_paths(
                        search_path, no_recurse)

            else:
                # populate files list using relative file path
                test_path = os.path.join(root_path, path_or_pattern)
                if not os.path.isdir(test_path):
                    yield test_path

                # populate files list using relative folder path
                else:
                    search_path = os.path.join(root_path, path_or_pattern,
                                               wildcard_pattern)
                    yield from PathHelper.find_include_paths(
                        search_path, no_recurse)