├── unibuild ├── projects │ ├── __init__.py │ ├── WixToolkit.py │ ├── fmtlib.py │ ├── spdlog.py │ ├── zlib.py │ ├── lz4.py │ ├── asmjit.py │ ├── googletest.py │ ├── sevenzip.py │ ├── udis86.py │ ├── cygwin.py │ ├── openssl.py │ ├── ncc.py │ ├── sip.py │ ├── boost.py │ ├── boostgit.py │ ├── python.py │ ├── icu.py │ ├── pyqt5.py │ └── qt5.py ├── utility │ ├── __init__.py │ ├── singleton.py │ ├── context_objects.py │ ├── enum.py │ ├── format_dict.py │ ├── progress_file.py │ ├── case_insensitive_dict.py │ └── lazy.py ├── __init__.py ├── modules │ ├── __init__.py │ ├── googlecode.py │ ├── sourceforge.py │ ├── repository.py │ ├── dummy.py │ ├── hg.py │ ├── github.py │ ├── Patch.py │ ├── b2.py │ ├── msbuild.py │ ├── git.py │ ├── urldownload.py │ ├── cmake.py │ └── build.py ├── dependency.py ├── retrieval.py ├── version.py ├── builder.py ├── progress.py ├── project.py ├── manager.py └── task.py ├── .gitignore ├── eggs └── __init__.py ├── README.md ├── config.py ├── libpatterns.py ├── unimake.py └── makefile.uni.py /unibuild/projects/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /unibuild/utility/__init__.py: -------------------------------------------------------------------------------- 1 | from format_dict import FormatDict 2 | from progress_file import ProgressFile 3 | from case_insensitive_dict import CIDict -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | downloads 3 | *.pyc 4 | progress 5 | Thumbs.db 6 | eggs/*.egg 7 | eggs/decorator 8 | graph.png 9 | .idea 10 | 7za.exe 11 | complete.uni.py 12 | -------------------------------------------------------------------------------- /unibuild/__init__.py: -------------------------------------------------------------------------------- 1 | from project import Project 2 | from dependency import Dependency 3 | from version import Version 4 | from task import Task 5 | from manager import TaskManager 6 | -------------------------------------------------------------------------------- /unibuild/modules/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Tannin' 2 | 3 | import os 4 | import glob 5 | 6 | modules = glob.glob(os.path.join(os.path.dirname(__file__), "*.py")) 7 | __all__ = [os.path.basename(f)[:-3] for f in modules] -------------------------------------------------------------------------------- /unibuild/utility/singleton.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Tannin' 2 | 3 | 4 | class Singleton(type): 5 | _instances = {} 6 | 7 | def __call__(cls, *args, **kwargs): 8 | if cls not in cls._instances: 9 | cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) 10 | return cls._instances[cls] 11 | -------------------------------------------------------------------------------- /unibuild/dependency.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Tannin' 2 | 3 | 4 | from project import Project 5 | 6 | 7 | class Dependency(Project): 8 | 9 | def __init__(self, name): 10 | super(Dependency, self).__init__(name) 11 | 12 | def applies(self, parameters): 13 | return True 14 | 15 | def version_eq(self, version): 16 | return self 17 | 18 | -------------------------------------------------------------------------------- /unibuild/modules/googlecode.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Tannin' 2 | 3 | 4 | from urldownload import URLDownload 5 | 6 | 7 | class Release(URLDownload): 8 | def __init__(self, project, filename, tree_depth=0): 9 | super(Release, self)\ 10 | .__init__("http://{project}.googlecode.com/files/{filename}".format(project=project, 11 | filename=filename) 12 | , tree_depth) 13 | -------------------------------------------------------------------------------- /unibuild/modules/sourceforge.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Tannin' 2 | 3 | 4 | from urldownload import URLDownload 5 | 6 | 7 | class Release(URLDownload): 8 | def __init__(self, project, path, tree_depth=0): 9 | super(Release, self)\ 10 | .__init__("http://downloads.sourceforge.net/project/{project}/{path}".format(project=project, 11 | path=path), 12 | tree_depth) 13 | -------------------------------------------------------------------------------- /unibuild/modules/repository.py: -------------------------------------------------------------------------------- 1 | from unibuild.retrieval import Retrieval 2 | from config import config 3 | import os 4 | 5 | 6 | class Repository(Retrieval): 7 | def __init__(self, url, branch): 8 | super(Repository, self).__init__() 9 | self._url = url 10 | self._branch = branch 11 | self._dir_name = os.path.basename(self._url) 12 | self._output_file_path = os.path.join(config["paths"]["build"], self._dir_name) 13 | 14 | @property 15 | def name(self): 16 | return "retrieve {0}".format(self._dir_name) 17 | 18 | -------------------------------------------------------------------------------- /unibuild/utility/context_objects.py: -------------------------------------------------------------------------------- 1 | from contextlib import contextmanager 2 | 3 | 4 | @contextmanager 5 | def on_failure(func): 6 | """ 7 | very generic context object generator that will run a parameterless lambda in case the 8 | wrapped code fails 9 | """ 10 | try: 11 | yield 12 | except: 13 | func() 14 | raise 15 | 16 | @contextmanager 17 | def on_exit(func): 18 | """ 19 | very generic context object generator that will run a parameterless lambda in case the 20 | wrapped code fails 21 | """ 22 | try: 23 | yield 24 | func() 25 | except: 26 | func() 27 | raise 28 | -------------------------------------------------------------------------------- /unibuild/retrieval.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Tannin' 2 | 3 | 4 | from task import Task 5 | from config import config 6 | import os 7 | 8 | 9 | class Retrieval(Task): 10 | 11 | def __init__(self): 12 | super(Retrieval, self).__init__() 13 | try: 14 | os.makedirs(config["paths"]["download"]) 15 | except Exception, e: 16 | # ignore error, probably means the path already exists 17 | pass 18 | 19 | def fulfilled(self): 20 | super(Retrieval, self).fulfilled() 21 | 22 | def applies(self, parameters): 23 | return True 24 | 25 | @property 26 | def name(self): 27 | return 28 | 29 | def process(self, progress): 30 | return 31 | -------------------------------------------------------------------------------- /unibuild/version.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Tannin' 2 | 3 | 4 | class Version(object): 5 | def __init__(self, version_string): 6 | self.__versionString = version_string 7 | 8 | def __eq__(self, other): 9 | return self.__versionString == other.__versionString 10 | 11 | def __ne__(self, other): 12 | return not self.__eq__(other) 13 | 14 | def __lt__(self, other): 15 | return self.__versionString < other.__versionString 16 | 17 | def __gt__(self, other): 18 | return self.__versionString > other.__versionString 19 | 20 | def __ge__(self, other): 21 | return self.__versionString >= other.__versionString 22 | 23 | def __le__(self, other): 24 | return self.__versionString <= other.__versionString -------------------------------------------------------------------------------- /unibuild/utility/enum.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | 19 | def enum(**enums): 20 | return type('Enum', (), enums) 21 | -------------------------------------------------------------------------------- /unibuild/utility/format_dict.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | 19 | class FormatDict(dict): 20 | """ 21 | a dictionary that doesn't throw an exception on access to an unknown key, 22 | intended to be used for format parameters. 23 | """ 24 | def __missing__(self, key): 25 | return "{" + key + "}" 26 | 27 | -------------------------------------------------------------------------------- /unibuild/builder.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | 19 | from task import Task 20 | 21 | 22 | class Builder(Task): 23 | 24 | def __init__(self): 25 | super(Builder, self).__init__() 26 | 27 | def applies(self, parameters): 28 | return True 29 | 30 | def name(self): 31 | return 32 | 33 | def process(self, progress): 34 | return 35 | -------------------------------------------------------------------------------- /unibuild/modules/dummy.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | 19 | from unibuild import Task 20 | 21 | 22 | class Success(Task): 23 | def __init__(self, name): 24 | super(Success, self).__init__() 25 | self.__name = name 26 | 27 | @property 28 | def name(self): 29 | return "dummy {}".format(self.__name) 30 | 31 | def process(self, progress): 32 | return True -------------------------------------------------------------------------------- /unibuild/projects/WixToolkit.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | 19 | from unibuild import Project 20 | from unibuild.modules import github 21 | from config import config 22 | import os 23 | 24 | 25 | WixToolSet_Version_Binary = config['WixToolSet_Version_Binary'] 26 | 27 | 28 | Project("WixToolkit") \ 29 | .depend(github.Release("wixtoolset", "wix3", "wix{}rtm".format(WixToolSet_Version_Binary), 30 | "wix{}-binaries".format(WixToolSet_Version_Binary)) 31 | .set_destination("WixToolkit")) 32 | -------------------------------------------------------------------------------- /unibuild/projects/fmtlib.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | 19 | from unibuild import Project 20 | from unibuild.modules import cmake, github 21 | from config import config 22 | 23 | Project("fmtlib") \ 24 | .depend(cmake.CMake().arguments( 25 | [ 26 | "-DCMAKE_INSTALL_PREFIX:PATH={}".format(config["paths"]["install"].replace('\\', '/')), 27 | "-DCMAKE_BUILD_TYPE={0}".format(config["build_type"]), 28 | ]).install() 29 | .depend(github.Source("fmtlib", "fmt", "3.0.0").set_destination("fmt") 30 | ) 31 | ) 32 | -------------------------------------------------------------------------------- /unibuild/utility/progress_file.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | 19 | import os 20 | 21 | 22 | class ProgressFile(file): 23 | 24 | def __init__(self, filename, progress_cb): 25 | super(ProgressFile, self).__init__(filename, "rb") 26 | 27 | assert callable(progress_cb) 28 | self.__progress_cb = progress_cb 29 | self.seek(0, os.SEEK_END) 30 | self.__size = self.tell() 31 | self.seek(0, os.SEEK_SET) 32 | 33 | def read(self, *args, **kwargs): 34 | self.__progress_cb(self.tell(), self.__size) 35 | 36 | return super(ProgressFile, self).read(*args, **kwargs) 37 | -------------------------------------------------------------------------------- /unibuild/projects/spdlog.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | # TODO This is really old, should by updated to 0.13 19 | 20 | from unibuild import Project 21 | from unibuild.modules import cmake, github 22 | from config import config 23 | 24 | Project("spdlog") \ 25 | .depend(cmake.CMake().arguments( 26 | [ 27 | "-DCMAKE_INSTALL_PREFIX:PATH={}".format(config["paths"]["install"].replace('\\', '/')), 28 | "-DCMAKE_BUILD_TYPE={0}".format(config["build_type"]), 29 | ]).install() 30 | .depend(github.Source("TanninOne", "spdlog", "master").set_destination("spdlog") 31 | ) 32 | ) 33 | -------------------------------------------------------------------------------- /unibuild/progress.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Tannin' 2 | 3 | 4 | class Progress(object): 5 | 6 | def __init__(self): 7 | self.__minimum = 0 8 | self.__maximum = 100 9 | self.__value = 0 10 | self.__job = "" 11 | self.__changeCallback = None 12 | 13 | @property 14 | def maximum(self): 15 | return self.__maximum 16 | 17 | @maximum.setter 18 | def maximum(self, new_value): 19 | self.__maximum = new_value 20 | 21 | @property 22 | def minimum(self): 23 | return self.__minimum 24 | 25 | @minimum.setter 26 | def minimum(self, new_value): 27 | self.__minimum = new_value 28 | 29 | @property 30 | def value(self): 31 | return self.__value 32 | 33 | @value.setter 34 | def value(self, new_value): 35 | self.__value = new_value 36 | self.__call_callback() 37 | 38 | @property 39 | def job(self): 40 | return self.__job 41 | 42 | @job.setter 43 | def job(self, new_job): 44 | self.__job = new_job 45 | self.__call_callback() 46 | 47 | def __call_callback(self): 48 | if self.__changeCallback is not None: 49 | self.__changeCallback(self.__job, self.__value * 100 / self.__maximum) 50 | 51 | def finish(self): 52 | self.__changeCallback(None, None) 53 | 54 | def set_change_callback(self, callback): 55 | self.__changeCallback = callback 56 | -------------------------------------------------------------------------------- /unibuild/projects/zlib.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | 19 | from unibuild import Project 20 | from unibuild.modules import cmake, urldownload 21 | from config import config 22 | import os 23 | 24 | 25 | zlib_version = config['zlib_version'] 26 | 27 | 28 | Project("zlib") \ 29 | .depend(cmake.CMake().arguments(["-DCMAKE_BUILD_TYPE={0}".format(config["build_type"]), 30 | "-DCMAKE_INSTALL_PREFIX:PATH={}".format( 31 | os.path.join(config["paths"]["build"], "zlib")) 32 | ]).install() 33 | .depend(urldownload.URLDownload("http://zlib.net/zlib-{}.tar.gz".format(zlib_version), 1))) 34 | -------------------------------------------------------------------------------- /unibuild/projects/lz4.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | 19 | from unibuild import Project 20 | from unibuild.modules import github, Patch 21 | from config import config 22 | import os 23 | 24 | 25 | lz4_version = "v1.7.4" 26 | 27 | Project("lz4") \ 28 | .depend(Patch.Copy(os.path.join(config['paths']['build'], "lz4", "dll", "liblz4.dll"), 29 | os.path.join(config["paths"]["install"], "bin", "dlls")) 30 | .depend(github.Release("lz4", "lz4", lz4_version, "lz4_{0}_win{1}".format(lz4_version.replace(".","_"),"64" if config['architecture'] == 'x86_64' else "32"),"zip") 31 | .set_destination("lz4") 32 | ) 33 | ) 34 | 35 | -------------------------------------------------------------------------------- /unibuild/projects/asmjit.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | 19 | from unibuild import Project 20 | from unibuild.modules import cmake, github 21 | from config import config 22 | 23 | 24 | # asmjit doesn't currently have any tags/branches but not every commit is usable 25 | asmjit_tag = "master" 26 | asmjit_commit = "fb9f82cb61df36aa513d054e748dc6769045f33e" 27 | 28 | Project("AsmJit") \ 29 | .depend(cmake.CMake().arguments( 30 | [ 31 | "-DASMJIT_STATIC=TRUE", 32 | "-DASMJIT_DISABLE_COMPILER=TRUE", 33 | "-DCMAKE_INSTALL_PREFIX:PATH={}".format(config["paths"]["install"].replace('\\', '/')), 34 | "-DCMAKE_BUILD_TYPE={0}".format(config["build_type"]), 35 | ]).install() 36 | .depend(github.Source("kobalicek", "asmjit", asmjit_tag, update=False, commit = asmjit_commit) 37 | .set_destination("asmjit")) 38 | ) 39 | 40 | -------------------------------------------------------------------------------- /unibuild/projects/googletest.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | 19 | from unibuild import Project 20 | from unibuild.modules import cmake, github, build 21 | from config import config 22 | import os 23 | import shutil 24 | import fnmatch 25 | 26 | 27 | googletest_version = "1.8.0" 28 | 29 | 30 | def install(context): 31 | for root, dirnames, filenames in os.walk(os.path.join(context['build_path'], "build")): 32 | for filename in fnmatch.filter(filenames, "*.lib"): 33 | shutil.copy(os.path.join(root, filename), os.path.join(config["paths"]["install"], "libs")) 34 | 35 | return True 36 | 37 | 38 | Project("GTest") \ 39 | .depend(build.Execute(install) 40 | .depend(cmake.CMake().arguments(["-Dgtest_force_shared_crt=ON", 41 | "-DCMAKE_BUILD_TYPE={0}".format(config["build_type"]) 42 | ]) 43 | .depend(github.Source("google", "googletest", "release-{}".format(googletest_version)))) 44 | ) 45 | 46 | -------------------------------------------------------------------------------- /eggs/__init__.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | import sys 3 | import urllib2 4 | import pip 5 | import tarfile 6 | from subprocess import call 7 | 8 | 9 | def download(url, filename): 10 | if os.path.exists(filename): 11 | return False 12 | 13 | data = urllib2.urlopen(url) 14 | with open(filename, 'wb') as outfile: 15 | while True: 16 | block = data.read(4096) 17 | if not block: 18 | break 19 | outfile.write(block) 20 | return True 21 | 22 | path = os.path.abspath(os.path.join(os.path.realpath(__file__), os.pardir)) 23 | 24 | 25 | for dep in ["https://gitlab.com/LePresidente/python-build-tools/uploads/18a195f7945ca35ad563b428739f254b/buildtools-0.0.2-py2.7.egg"]: 26 | eggpath = os.path.join(path, os.path.basename(dep)) 27 | download(dep, eggpath) 28 | sys.path.append(eggpath) 29 | 30 | for dep in ["decorator", "lxml", "PyYAML", "six", "jinja2", "psutil", "patch", "networkx","pydot"]: 31 | destpath = "{0}/{1}".format(path, dep) 32 | if not os.path.exists(destpath): 33 | pip.main(["install", "--target={0}".format(destpath), dep]) 34 | sys.path.append(destpath) 35 | 36 | """ neither of these work. particularly building pygraphviz requires a specific VC version in a specific location 37 | 38 | 39 | for dep in ["pygraphviz"]: 40 | pip.main(["install", "--install-option=\"--prefix={}\"".format(path), dep]) 41 | 42 | 43 | for dep in ["https://pypi.python.org/packages/source/p/pygraphviz/pygraphviz-1.3.1.tar.gz"]: 44 | basename = os.path.basename(dep) 45 | libpath = os.path.join(path, basename) 46 | if download(dep, libpath): 47 | with tarfile.open(libpath, 'r') as tar: 48 | tar.extractall(path=path) 49 | cwd = os.path.join(path, os.path.splitext(os.path.splitext(basename)[0])[0]) 50 | call(["python", "setup.py", "install"], cwd=cwd) 51 | """ 52 | -------------------------------------------------------------------------------- /unibuild/projects/sevenzip.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | 19 | from unibuild import Project 20 | from unibuild.modules import urldownload, build, Patch 21 | from config import config 22 | import os 23 | 24 | 25 | # newer versions are beta as of now. They have slightly (?) different api as well 26 | sevenzip_version = "16.04" 27 | 28 | # TODO build sevenzip, we require the dll in install/bin/dlls. 29 | # sevenzip is not built here as we only use its source 30 | Project("7zip") \ 31 | .depend(Patch.Copy(os.path.join(config['paths']['build'], "7zip", "CPP", "7zip", "Bundles", "Format7zF", "{}" 32 | .format("x86" if config['architecture'] == 'x86' else "AMD64"),"7z.dll"), 33 | os.path.join(config["paths"]["install"], "bin","dlls")) 34 | .depend(build.Run(r"nmake CPU={} NEW_COMPILER=1 MY_STATIC_LINK=1 NO_BUFFEROVERFLOWU=1".format("x86" if config['architecture'] == 'x86' else "AMD64"), 35 | working_directory=os.path.join(config['paths']['build'], "7zip", "CPP", "7zip")) 36 | .depend(urldownload.URLDownload("http://www.7-zip.org/a/7z{}-src.7z".format(sevenzip_version.replace(".", ""))).set_destination("7zip")))) 37 | -------------------------------------------------------------------------------- /unibuild/modules/hg.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | 19 | from unibuild.modules.repository import Repository 20 | from subprocess import Popen 21 | from config import config 22 | import os 23 | import logging 24 | 25 | 26 | class Clone(Repository): 27 | def __init__(self, url, branch="default"): 28 | super(Clone, self).__init__(url, branch) 29 | 30 | def prepare(self): 31 | self._context["build_path"] = self._output_file_path 32 | 33 | def process(self, progress): 34 | if os.path.isdir(self._output_file_path): 35 | proc = Popen([config['paths']['hg'], "pull", "-u"], 36 | cwd=self._output_file_path, 37 | env=config["__environment"]) 38 | else: 39 | proc = Popen([config['paths']['hg'], "clone", "-b", self._branch, 40 | self._url, self._context["build_path"]], 41 | env=config["__environment"]) 42 | proc.communicate() 43 | if proc.returncode != 0: 44 | logging.error("failed to clone repository %s (returncode %s)", self._url, proc.returncode) 45 | return False 46 | 47 | return True 48 | 49 | @staticmethod 50 | def _expiration(): 51 | return config.get('repo_update_frequency', 60 * 60 * 24) # default: one day 52 | 53 | def set_destination(self, destination_name): 54 | self._output_file_path = os.path.join(config["paths"]["build"], destination_name) 55 | return self 56 | -------------------------------------------------------------------------------- /unibuild/project.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | 19 | from task import Task 20 | from manager import TaskManager, register_project 21 | 22 | 23 | class Project(Task): 24 | 25 | def __init__(self, name): 26 | super(Project, self).__init__() 27 | self.__name = name 28 | self.__context_data = {} 29 | self.__enabled = False 30 | register_project(self) 31 | 32 | @property 33 | def name(self): 34 | return self.__name 35 | 36 | @property 37 | def enabled(self): 38 | return self.__enabled 39 | 40 | @enabled.setter 41 | def enabled(self, value): 42 | self.__enabled = value 43 | 44 | def __getitem__(self, key): 45 | return self.__context_data[key] 46 | 47 | def __setitem__(self, key, value): 48 | self.__context_data[key] = value 49 | 50 | def __contains__(self, keys): 51 | return self.__context_data.__contains__(keys) 52 | 53 | def set_context_item(self, key, value): 54 | self.__context_data[key] = value 55 | return self 56 | 57 | def applies(self, parameters): 58 | return True 59 | 60 | def process(self, progress): 61 | return True 62 | 63 | def depend(self, task): 64 | if type(task) == str: 65 | task_obj = TaskManager().get_task(task) 66 | if task_obj is None: 67 | raise KeyError("unknown project \"{}\"".format(task)) 68 | else: 69 | task = task_obj 70 | else: 71 | task.set_context(self) 72 | return super(Project, self).depend(task) 73 | -------------------------------------------------------------------------------- /unibuild/modules/github.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | 19 | from urldownload import URLDownload 20 | from git import Clone 21 | 22 | 23 | class Release(URLDownload): 24 | def __init__(self, author, project, version, filename, extension="zip", tree_depth=0): 25 | super(Release, self) \ 26 | .__init__("https://github.com/{author}/{project}/releases/download/{version}/" 27 | "{filename}.{extension}".format(author=author, 28 | project=project, 29 | version=version, 30 | filename=filename, 31 | extension=extension),tree_depth) 32 | 33 | 34 | class Source(Clone): 35 | def __init__(self, author, project, branch="master", super_repository=None, update=True, commit=None): 36 | super(Source, self).__init__("https://github.com/{author}/{project}.git".format(author=author, 37 | project=project), 38 | branch, super_repository, update, commit) 39 | #super(Source, self).__init__("https://github.com/{author}/{project}/archive/{tag}.zip".format(), 1) 40 | # don't use the tag as the file name, otherwise we get name collisions on "master" or other generic names 41 | #self.set_destination(project) 42 | 43 | 44 | 45 | # TODO never supported checking out by tag, should create new class here. (required by asmjit) 46 | -------------------------------------------------------------------------------- /unibuild/manager.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | 19 | import networkx as nx 20 | from utility.singleton import Singleton 21 | 22 | 23 | class TaskManager(object): 24 | """ 25 | manages task dependency graph 26 | """ 27 | __metaclass__ = Singleton 28 | 29 | def __init__(self): 30 | self.__topLevelTask = [] 31 | 32 | def add_task(self, task): 33 | self.__topLevelTask.append(task) 34 | 35 | def get_task(self, name): 36 | for task in self.__topLevelTask: 37 | if task.name == name: 38 | return task 39 | return None 40 | 41 | def create_graph(self, parameters): 42 | graph = nx.DiGraph() 43 | for task in self.__topLevelTask: 44 | self.__add_task(graph, task, parameters, 0) 45 | 46 | graph.concentrate = True 47 | return graph 48 | 49 | def enable(self, graph, node): 50 | """ 51 | recursively enable the node 52 | :param graph: 53 | :param node: 54 | :return: 55 | """ 56 | for suc in graph.successors_iter(node): 57 | self.enable(graph, suc) 58 | graph.node[node]["enable"] = True 59 | 60 | def enable_all(self, graph): 61 | for node in graph.nodes_iter(): 62 | if graph.in_degree(node) == 0: 63 | self.enable(graph, node) 64 | 65 | def __add_task(self, graph, task, parameters, level): 66 | if not graph.has_node(task.name): 67 | graph.add_node(task.name, color='red' if level == 0 else 'blue', peripheries=max(1, 2 - level), 68 | task=task, enable=False) 69 | 70 | for dependency in task.dependencies: 71 | self.__add_task(graph, dependency, parameters, level + 1) 72 | graph.add_edge(task.name, dependency.name) 73 | 74 | 75 | def register_project(task): 76 | TaskManager().add_task(task) 77 | -------------------------------------------------------------------------------- /unibuild/projects/udis86.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | 19 | from unibuild import Project 20 | from unibuild.modules import build, sourceforge 21 | from config import config 22 | from glob import glob 23 | import shutil 24 | import os 25 | import errno 26 | 27 | 28 | udis_version = "1.7" 29 | udis_version_minor = "2" 30 | 31 | 32 | def make_sure_path_exists(path): 33 | try: 34 | os.makedirs(path) 35 | except OSError as exception: 36 | if exception.errno != errno.EEXIST: 37 | raise 38 | 39 | 40 | def install(context): 41 | make_sure_path_exists(os.path.join(config["paths"]["install"], "libs")) 42 | for f in glob(os.path.join(context['build_path'], "*.lib")): 43 | shutil.copy(f, os.path.join(config["paths"]["install"], "libs")) 44 | return True 45 | 46 | 47 | Project("Udis86") \ 48 | .depend(build.Execute(install) 49 | .depend((build.CPP().type(build.STATIC_LIB) 50 | .sources("libudis86", ["libudis86/decode.c", 51 | "libudis86/itab.c", 52 | "libudis86/syn.c", 53 | "libudis86/syn-att.c", 54 | "libudis86/syn-intel.c", 55 | "libudis86/udis86.c"]) 56 | .custom("libudis86/itab.c", 57 | cmd="{python} scripts/ud_itab.py docs/x86/optable.xml" 58 | " libudis86".format(**config["__environment"])) 59 | ) 60 | .depend(sourceforge.Release("udis86", "udis86/{0}/udis86-{0}.{1}.tar.gz".format(udis_version, 61 | udis_version_minor), 62 | tree_depth=1)) 63 | ) 64 | ) 65 | -------------------------------------------------------------------------------- /unibuild/utility/case_insensitive_dict.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | 19 | class CIDict(dict): 20 | """ 21 | case insensitive dictionary 22 | based on this Stack Overflow post: 23 | http://stackoverflow.com/questions/2082152/case-insensitive-dictionary/32888599 24 | """ 25 | def __init__(self, *args, **kwargs): 26 | super(CIDict, self).__init__(*args, **kwargs) 27 | self.__convert_keys() 28 | 29 | def copy(self): 30 | return self.__class__(super(CIDict, self).copy()) 31 | 32 | def __getitem__(self, key): 33 | return super(CIDict, self).__getitem__(self.__class__.__key(key)) 34 | 35 | def __setitem__(self, key, value): 36 | super(CIDict, self).__setitem__(self.__class__.__key(key), value) 37 | 38 | def __delitem__(self, key): 39 | return super(CIDict, self).__delitem__(self.__class__.__key(key)) 40 | 41 | def __contains__(self, key): 42 | return super(CIDict, self).__contains__(self.__class__.__key(key)) 43 | 44 | def has_key(self, key): 45 | return super(CIDict, self).__contains__(self.__class__.__key(key)) 46 | 47 | def pop(self, key, *args, **kwargs): 48 | return super(CIDict, self).pop(self.__class__.__key(key), *args, **kwargs) 49 | 50 | def get(self, key, *args, **kwargs): 51 | return super(CIDict, self).get(self.__class__.__key(key), *args, **kwargs) 52 | 53 | def setdefault(self, key, *args, **kwargs): 54 | return super(CIDict, self).setdefault(self.__class__.__key(key), *args, **kwargs) 55 | 56 | def update(self, e=None, **f): 57 | super(CIDict, self).update(self.__class__(e)) 58 | super(CIDict, self).update(self.__class__(**f)) 59 | 60 | @classmethod 61 | def __key(cls, key): 62 | return key.lower() if isinstance(key, basestring) else key 63 | 64 | def __convert_keys(self): 65 | for k in list(self.keys()): 66 | v = super(CIDict, self).pop(k) 67 | self.__setitem__(k, v) 68 | -------------------------------------------------------------------------------- /unibuild/modules/Patch.py: -------------------------------------------------------------------------------- 1 | from unibuild.task import Task 2 | from unibuild.utility.lazy import Lazy 3 | import os.path 4 | import shutil 5 | 6 | 7 | class Replace(Task): 8 | 9 | def __init__(self, filename, search, substitute): 10 | super(Replace, self).__init__() 11 | self.__file = filename 12 | self.__search = search 13 | self.__substitute = substitute 14 | 15 | @property 16 | def name(self): 17 | return "Replace in {}".format(self.__file) 18 | 19 | def process(self, progress): 20 | full_path = os.path.join(self._context["build_path"], self.__file) 21 | with open(full_path, "r") as f: 22 | data = f.read() 23 | 24 | data = data.replace(self.__search, self.__substitute) 25 | 26 | with open(full_path, "w") as f: 27 | f.write(data) 28 | return True 29 | 30 | 31 | class Copy(Task): 32 | 33 | def __init__(self, source, destination): 34 | super(Copy, self).__init__() 35 | if isinstance(source, str): 36 | source = [source] 37 | self.__source = Lazy(source) 38 | self.__destination = Lazy(destination) 39 | 40 | @property 41 | def name(self): 42 | if self.__source.type() == list: 43 | return "Copy {}...".format(self.__source()[0]) 44 | else: 45 | return "Copy {}".format(self.__source.peek()) 46 | 47 | def process(self, progress): 48 | if os.path.isabs(self.__destination()): 49 | full_destination = self.__destination() 50 | else: 51 | full_destination = os.path.join(self._context["build_path"], self.__destination()) 52 | 53 | for source in self.__source(): 54 | if not os.path.isabs(source): 55 | source = os.path.join(self._context["build_path"], source) 56 | if not os.path.exists(full_destination): 57 | os.makedirs(full_destination) 58 | shutil.copy(source, full_destination) 59 | return True 60 | 61 | 62 | class CreateFile(Task): 63 | def __init__(self, filename, content): 64 | super(CreateFile, self).__init__() 65 | self.__filename = filename 66 | self.__content = Lazy(content) 67 | 68 | @property 69 | def name(self): 70 | if self._context is not None: 71 | return "Create File {}-{}".format(self._context.name, self.__filename) 72 | else: 73 | return "Create File {}".format(self.__filename) 74 | 75 | def process(self, progress): 76 | full_path = os.path.join(self._context["build_path"], self.__filename) 77 | with open(full_path, 'w') as f: 78 | # the call to str is necessary to ensure a lazy initialised content is evaluated now 79 | f.write(self.__content()) 80 | 81 | return True 82 | -------------------------------------------------------------------------------- /unibuild/utility/lazy.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | 19 | class Get(object): 20 | def __init__(self, dictionary, key): 21 | super(Get, self).__init__() 22 | self.__dict = dictionary 23 | self.__key = key 24 | 25 | def __get__(self, instance, owner): 26 | return self.__dict[self.__key] 27 | 28 | 29 | class Evaluate(object): 30 | def __init__(self, func): 31 | super(Evaluate, self).__init__() 32 | self.__data = None 33 | self.__func = func 34 | 35 | def __evaluate(self): 36 | if self.__data is None: 37 | self.__data = self.__func() 38 | 39 | def __getattr__(self, item): 40 | self.__evaluate() 41 | return getattr(self.__data, item) 42 | 43 | def __getitem__(self, item): 44 | self.__evaluate() 45 | return self.__data[item] 46 | 47 | def __str__(self): 48 | self.__evaluate() 49 | return str(self.__data) 50 | 51 | def __len__(self): 52 | if self.__data is not None: 53 | return len(self.__data) 54 | else: 55 | return 0 56 | 57 | def __iter__(self): 58 | self.__evaluate() 59 | return iter(self.__data) 60 | 61 | def __add__(self, other): 62 | self.__evaluate() 63 | return self.__data + other 64 | 65 | 66 | class Lazy(object): 67 | def __init__(self, val): 68 | if callable(val): 69 | self.__value = None 70 | self.__func = val 71 | else: 72 | self.__value = val 73 | self.__func = None 74 | 75 | def __call__(self): 76 | if self.__func is not None: 77 | self.__value = self.__func() 78 | self.__func = None 79 | return self.__value 80 | 81 | def type(self): 82 | if self.__value is None: 83 | return None 84 | else: 85 | return type(self.__value) 86 | 87 | def peek(self): 88 | if self.__value is None: 89 | return self.__func.func_doc 90 | else: 91 | return self.__value 92 | 93 | 94 | def doclambda(func, doc): 95 | func.__doc__ = doc 96 | return func -------------------------------------------------------------------------------- /unibuild/projects/cygwin.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | 19 | from unibuild import Project 20 | from unibuild.modules import urldownload, build 21 | from config import config 22 | from subprocess import Popen 23 | import os 24 | import logging 25 | import time 26 | import shutil 27 | 28 | 29 | # installation happens concurrently in separate process. We need to wait for all relevant files to exist, 30 | # and can determine failure only by timeout 31 | timeout = 15 # seconds 32 | 33 | 34 | def bitness(): 35 | return "64" if config['architecture'] == "x86_64" else "32" 36 | 37 | filename = "setup-{}.exe".format(config['architecture']) 38 | 39 | url = "http://www.cygwin.com/{}".format(filename) 40 | 41 | Cygwin_Mirror = "http://mirrors.kernel.org/sourceware/cygwin/" 42 | 43 | 44 | def build_func(context): 45 | proc = Popen([os.path.join(config['paths']['download'], filename), 46 | "-q","-C", "Base", "-P", "make,dos2unix,binutils", "-n", "-d", "-O", "-B", "-R", "{}/../cygwin" 47 | .format(context['build_path']), "-l", "{}".format(os.path.join(config['paths']['download'])), 48 | "-s", "{}".format(Cygwin_Mirror)],env=config['__environment']) 49 | proc.communicate() 50 | if proc.returncode != 0: 51 | logging.error("failed to run installer (returncode %s)", 52 | proc.returncode) 53 | return False 54 | dos2unix_path = os.path.join(context['build_path'],"../cygwin","bin", "dos2unix.exe") 55 | make_path = os.path.join(context['build_path'],"../cygwin", "bin", "make.exe") 56 | wait_counter = timeout 57 | while wait_counter > 0: 58 | if os.path.isfile(dos2unix_path) and os.path.isfile(make_path): 59 | break 60 | else: 61 | time.sleep(5.0) 62 | wait_counter -= 5 63 | # wait a bit longer because the installer may have been in the process of writing the file 64 | time.sleep(5.0) 65 | 66 | if wait_counter<=0: 67 | logging.error("Unpacking of Cygwin timed out"); 68 | return False #We timed out and nothing was installed 69 | 70 | return True 71 | 72 | 73 | cygwin = Project("cygwin")\ 74 | .depend(build.Execute(build_func) 75 | .depend(urldownload.URLDownload(url)) 76 | ) 77 | 78 | -------------------------------------------------------------------------------- /unibuild/modules/b2.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | 19 | from unibuild.builder import Builder 20 | from subprocess import Popen 21 | import os 22 | import logging 23 | 24 | 25 | class B2(Builder): 26 | 27 | def __init__(self,name=None): 28 | super(B2, self).__init__() 29 | self.__arguments = [] 30 | self.__name = name 31 | 32 | @property 33 | def name(self): 34 | if self._context is None: 35 | return "b2" 36 | else: 37 | return "b2 {}_{}".format(self._context.name, self.__name) 38 | 39 | 40 | 41 | def applies(self, parameters): 42 | return True 43 | 44 | def fulfilled(self): 45 | return False 46 | 47 | def arguments(self, arguments): 48 | if arguments is None: 49 | self.__arguments = [] 50 | else: 51 | self.__arguments = arguments 52 | return self 53 | 54 | def process(self, progress): 55 | if "build_path" not in self._context: 56 | logging.error("source path not known for {}," 57 | " are you missing a matching retrieval script?".format(self.name())) 58 | soutpath = os.path.join(self._context["build_path"], "stdout.log") 59 | serrpath = os.path.join(self._context["build_path"], "stderr.log") 60 | with open(soutpath, "a") as sout: 61 | with open(serrpath, "a") as serr: 62 | proc = Popen(["cmd.exe", "/C", "bootstrap.bat"], cwd=self._context["build_path"], 63 | stdout=sout, stderr=serr) 64 | proc.communicate() 65 | if proc.returncode != 0: 66 | logging.error("failed to bootstrap (returncode %s), see %s and %s", 67 | proc.returncode, soutpath, serrpath) 68 | return False 69 | 70 | cmdline = ["b2.exe"] 71 | if self.__arguments: 72 | cmdline.extend(self.__arguments) 73 | 74 | proc = Popen(cmdline, cwd=self._context["build_path"], stdout=sout, stderr=serr, shell=True) 75 | proc.communicate() 76 | if proc.returncode != 0: 77 | logging.error("failed to build (returncode %s), see %s and %s", 78 | proc.returncode, soutpath, serrpath) 79 | return False 80 | return True 81 | -------------------------------------------------------------------------------- /unibuild/projects/openssl.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | 19 | from unibuild import Project 20 | from unibuild.modules import urldownload, build, Patch 21 | from config import config 22 | from subprocess import Popen 23 | import os 24 | import logging 25 | import time 26 | import shutil 27 | 28 | 29 | # currently binary installation only 30 | 31 | 32 | openssl_version = config['openssl_version'] 33 | 34 | libeay = "libeay32MD.lib" 35 | ssleay = "ssleay32MD.lib" 36 | # installation happens concurrently in separate process. We need to wait for all relevant files to exist, 37 | # and can determine failure only by timeout 38 | timeout = 15 # seconds 39 | 40 | 41 | def bitness(): 42 | return "64" if config['architecture'] == "x86_64" else "32" 43 | 44 | filename = "Win{}OpenSSL-{}.exe".format(bitness(), openssl_version.replace(".", "_")) 45 | 46 | url = "https://slproweb.com/download/{}".format(filename) 47 | 48 | 49 | def build_func(context): 50 | proc = Popen([os.path.join(config['paths']['download'], filename), 51 | "/VERYSILENT", "/DIR={}".format(context['build_path'])], 52 | env=config['__environment']) 53 | proc.communicate() 54 | if proc.returncode != 0: 55 | logging.error("failed to run installer (returncode %s)", 56 | proc.returncode) 57 | return False 58 | libeay_path = os.path.join(context['build_path'], "lib", "VC", "static", libeay) 59 | ssleay_path = os.path.join(context['build_path'], "lib", "VC", "static", ssleay) 60 | wait_counter = timeout 61 | while wait_counter > 0: 62 | if os.path.isfile(libeay_path) and os.path.isfile(ssleay_path): 63 | break 64 | else: 65 | time.sleep(1.0) 66 | wait_counter -= 1 67 | # wait a bit longer because the installer may have been in the process of writing the file 68 | time.sleep(1.0) 69 | 70 | if wait_counter<=0: 71 | logging.error("Unpacking of OpenSSL timed out"); 72 | return False #We timed out and nothing was installed 73 | 74 | return True 75 | 76 | 77 | openssl = Project("openssl") \ 78 | .depend(Patch.Copy([os.path.join(config['paths']['build'], "Win{0}OpenSSL-{1}" 79 | .format("32" if config['architecture'] == 'x86' else "64", 80 | openssl_version.replace(".","_")), "ssleay32.dll"), 81 | os.path.join(config['paths']['build'], "Win{0}OpenSSL-{1}" 82 | .format("32" if config['architecture'] == 'x86' else "64", 83 | openssl_version.replace(".", "_")), "libeay32.dll"), ], 84 | os.path.join(config["paths"]["install"], "bin","dlls")) 85 | .depend(build.Execute(build_func) 86 | .depend(urldownload.URLDownload(url)) 87 | )) 88 | 89 | -------------------------------------------------------------------------------- /unibuild/modules/msbuild.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | 19 | from build import Builder 20 | from subprocess import Popen 21 | from config import config 22 | import shutil 23 | import logging 24 | import os 25 | 26 | 27 | class MSBuild(Builder): 28 | 29 | def __init__(self, solution, project=None, working_directory=None, project_platform=None, project_PlatformToolset=None ): 30 | super(MSBuild, self).__init__() 31 | self.__solution = solution 32 | self.__project = project 33 | self.__working_directory = working_directory 34 | self.__project_platform = project_platform 35 | self.__project_platformtoolset = project_PlatformToolset 36 | 37 | @property 38 | def name(self): 39 | if self._context is None: 40 | return "msbuild" 41 | else: 42 | return "msbuild {0}".format(self._context.name) 43 | 44 | def applies(self, parameters): 45 | return True 46 | 47 | def fulfilled(self): 48 | return False 49 | 50 | def process(self, progress): 51 | if "build_path" not in self._context: 52 | logging.error("source path not known for {}," 53 | " are you missing a matching retrieval script?".format(self._context.name)) 54 | return False 55 | 56 | soutpath = os.path.join(self._context["build_path"], "stdout.log") 57 | serrpath = os.path.join(self._context["build_path"], "stderr.log") 58 | 59 | with open(soutpath, "w") as sout: 60 | with open(serrpath, "w") as serr: 61 | args = ["msbuild", self.__solution, "/m", "/property:Configuration=Release"] 62 | 63 | if self.__project_platform is None: 64 | args.append("/property:Platform={}" 65 | .format("x64" if config['architecture'] == 'x86_64' else "win32")) 66 | else: 67 | args.append("/property:Platform={}".format(self.__project_platform)) 68 | 69 | if self.__project_platformtoolset is not None: 70 | args.append("/property:PlatformToolset={}" 71 | .format(self.__project_platformtoolset)) 72 | 73 | if self.__project: 74 | args.append("/target:{}".format(self.__project)) 75 | 76 | proc = Popen( 77 | args, 78 | shell=True, 79 | cwd=str(self.__working_directory or self._context["build_path"]), 80 | env=config["__environment"], 81 | stdout=sout, stderr=serr) 82 | proc.communicate() 83 | if proc.returncode != 0: 84 | logging.error("failed to generate makefile (returncode %s), see %s and %s", 85 | proc.returncode, soutpath, serrpath) 86 | return False 87 | 88 | return True 89 | -------------------------------------------------------------------------------- /unibuild/projects/ncc.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | import eggs 19 | from unibuild import Project 20 | from unibuild.modules import build, msbuild, Patch, github 21 | from unibuild.utility import lazy 22 | from config import config 23 | import os 24 | from buildtools import log 25 | from buildtools.buildsystem.visualstudio import (ProjectType, 26 | VisualStudio2015Solution, 27 | VS2015Project) 28 | 29 | 30 | def prepare_nmm(context): 31 | sln = VisualStudio2015Solution() 32 | sln.LoadFromFile(os.path.join(context['build_path'],'NexusClient.sln')) 33 | ncc_csproj = os.path.join(context['build_path'],"..",'NexusClientCLI', 'NexusClientCLI', 'NexusClientCLI.csproj') 34 | if not os.path.isfile(ncc_csproj): 35 | log.critical('NOT FOUND: %s', ncc_csproj) 36 | else: 37 | log.info('FOUND: %s', ncc_csproj) 38 | changed = False 39 | projfile = VS2015Project() 40 | projfile.LoadFromFile(ncc_csproj) 41 | projguid = projfile.PropertyGroups[0].element.find('ProjectGuid').text 42 | log.info('ProjectGuid = %s', projguid) 43 | if "NexusClientCli" not in sln.projectsByName: 44 | newproj = sln.AddProject('NexusClientCli', ProjectType.CSHARP_PROJECT, ncc_csproj, guid=projguid) 45 | log.info('Adding project %s (%s) to NexusClient.sln', newproj.name, newproj.guid) 46 | changed = True 47 | else: 48 | newproj = sln.projectsByName['NexusClientCli'] 49 | log.info('Project %s (%s) already exists in NexusClient.sln', newproj.name, newproj.guid) 50 | if newproj.projectfile != ncc_csproj: 51 | log.info('Changing projectfile: %s -> %s', newproj.projectfile, ncc_csproj) 52 | newproj.projectfile = ncc_csproj 53 | changed = True 54 | if changed: 55 | log.info('Writing NexusClientCli.sln') 56 | sln.SaveToFile(os.path.relpath(os.path.join(ncc['build_path'],"..","nmm",'NexusClientCli.sln'))) # So we don't get conflicts when pulling. 57 | return True 58 | 59 | 60 | init_repos = github.Source("Nexus-Mods", "Nexus-Mod-Manager", "master") \ 61 | .set_destination(os.path.join("NCC", "nmm")) 62 | 63 | 64 | ncc = Project("NCC") \ 65 | .depend(build.Run(r"publish.bat" 66 | .format("-debug" if config['build_type'] == "Debug" else "-release", 67 | os.path.join(config["paths"]["install"], "bin")), 68 | working_directory=lazy.Evaluate(lambda: os.path.join(ncc['build_path'], "..", "NexusClientCli"))) 69 | .depend(msbuild.MSBuild(os.path.join(config['paths']['build'],"NCC","nmm",'NexusClientCli.sln'), 70 | working_directory=lazy.Evaluate(lambda: os.path.join(ncc['build_path'], "..", "nmm")),project_platform="Any CPU") 71 | .depend(build.Execute(prepare_nmm, name="append NexusClientCli project to NMM") 72 | 73 | .depend(init_repos).depend(github.Source(config['Main_Author'], "modorganizer-NCC", "master") \ 74 | .set_destination(os.path.join("NCC", "NexusClientCli")) 75 | )))) 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /unibuild/projects/sip.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | 19 | from unibuild import Project 20 | from unibuild.modules import sourceforge, build 21 | from subprocess import Popen 22 | from config import config 23 | from glob import glob 24 | import os 25 | import logging 26 | import python 27 | import errno 28 | import shutil 29 | 30 | 31 | sip_version = "4.19.1" 32 | python_version = config.get('python_version', "2.7") + config.get('python_version_minor', ".13") 33 | 34 | 35 | def sip_environment(): 36 | result = config['__environment'].copy() 37 | result['LIB'] += os.path.join(config['paths']['build'],"python-{}".format(python_version),"PCbuild","amd64") 38 | return result 39 | 40 | 41 | def make_sure_path_exists(path): 42 | try: 43 | os.makedirs(path) 44 | except OSError as exception: 45 | if exception.errno != errno.EEXIST: 46 | raise 47 | 48 | 49 | def copy_pyd(context): 50 | make_sure_path_exists(os.path.join(config["__build_base_path"], "install", "bin", "plugins", "data")) 51 | for f in glob(os.path.join(python.python['build_path'], "Lib", "site-packages", "sip.pyd")): 52 | shutil.copy(f, os.path.join(config["__build_base_path"], "install", "bin", "plugins", "data")) 53 | return True 54 | 55 | 56 | class SipConfigure(build.Builder): 57 | def __init__(self): 58 | super(SipConfigure, self).__init__() 59 | 60 | @property 61 | def name(self): 62 | return "sip configure" 63 | 64 | def process(self, progress): 65 | soutpath = os.path.join(self._context["build_path"], "stdout.log") 66 | serrpath = os.path.join(self._context["build_path"], "stderr.log") 67 | with open(soutpath, "w") as sout: 68 | with open(serrpath, "w") as serr: 69 | bp = python.python['build_path'] 70 | 71 | proc = Popen([os.path.join(python.python['build_path'],"PCbuild","amd64","python.exe"), "configure.py", 72 | "-b", bp, 73 | "-d", os.path.join(bp, "Lib", "site-packages"), 74 | "-v", os.path.join(bp, "sip"), 75 | "-e", os.path.join(bp, "include") 76 | ], 77 | env=config["__environment"], 78 | cwd=self._context["build_path"], 79 | shell=True, 80 | stdout=sout, stderr=serr) 81 | proc.communicate() 82 | if proc.returncode != 0: 83 | logging.error("failed to run sip configure.py (returncode %s), see %s and %s", 84 | proc.returncode, soutpath, serrpath) 85 | return False 86 | 87 | return True 88 | 89 | 90 | Project('sip') \ 91 | .depend(build.Execute(copy_pyd) 92 | .depend(build.Make(environment=sip_environment()).install() 93 | .depend(SipConfigure() 94 | .depend("Python") 95 | .depend(sourceforge.Release("pyqt", 96 | "sip/sip-{0}/sip-{0}.zip".format(sip_version), 97 | 1) 98 | ) 99 | ) 100 | ) 101 | ) 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # modorganizer-umbrella 2 | An umbrella- (super-) project for modorganizer. 3 | 4 | ##Build instructions for all required Components 5 | 6 | Build Instructions: 7 | unimake.py -d "F:\Build" 8 | 9 | ## Purpose 10 | This repository contains a meta build-system that is able to download and build MO subprojects and dependencies as necessary. 11 | It can be used to build the whole project to produce a build that should be equivalent to the release or to build subprojects (i.e. plugins) with the minimum of dependencies. 12 | 13 | This umbrella project can optionally produce ide projects for developers. 14 | 15 | ## Notes 16 | * While mostly functional this project is work in progress and should only be used by those with the will to spend some time. 17 | * Currently all dependencies are built from source, including monsters like Qt and python. This is necessary to produce releasable bundles (pre-built python would introduce additional run-time dependencies, pre-built Qt doesn't provide debug symbols (pdbs)) but it is overkill if all you want to do is contribute to a plugin. 18 | 19 | ## Concept 20 | At its base this is actually a fairly simple program. Arbitrary command calls are wrapped inside Task objects (grouped as projects) and put into a dependency graph. 21 | The tasks are then executed in proper order, with the umbrella providing the environment so you don't need to have all required tool in your global PATH variable. 22 | 23 | There are specialised task implementations to conveniently specify sources to be retrieved from a repository or to get the correct make tool invoked. 24 | 25 | Now one thing that may be a bit confusing is that all tasks have to be fully initialized before processing starts but since tasks will usually build upon each other, not all information may be available at that time. 26 | In these cases functions/lambdas can be passed as parameters in task initialization which will then be invoked when that task is processed which will be after all dependencies are complete. 27 | 28 | Some more details: 29 | - Successfully completed tasks are memorized (in the "progress" directory) and will not be run again 30 | - Names for tasks are generated so they may not be very user-friendly 31 | - Technically, independent tasks could be executed in parallel but that is not (yet) implemented 32 | 33 | ## Open Problems 34 | 35 | While conceptually this isn't particularly complicated, the actual build process for some tools are massively complex. Some issues I have not been able to work around yet: 36 | - I can't seem to "make install" the qt webkit subproject properly. After building I have to manually move around files to impossible locations (#include "../../../../Source/and/so/on"-the f???) 37 | - The windows variant of grep depends on dlls and the way the qt build system calls it they apparently can't be found even if they reside in the same directory as the exe. 38 | - If openssl fails to install. Make sure you either click yes to the UAC message, or disable UAC in windows 39 | 40 | ## Dependencies 41 | 42 | Windows 7 and up (64bit) 43 | 44 | All the following need to be either on your PATH, or available from Program Files, Program Files (x86), C:\ or D:\. Note that apart from ruby, these things install themselves in Program Files or Program Files (x86) by default so ruby is the only one you might need to be careful about. 45 | 46 | * python 2.7 (2.7.13 x64) 47 | * decorator 48 | * visual Studio 2015 (Including VC Common tools) 49 | 50 | * cmake 51 | * 7zip - specifically, the command line version (7za) 52 | * svn (SlinkSVN) 53 | * ruby (v2.2 x64) 54 | * git 55 | * perl (Strawberry Perl) 56 | 57 | ## Usage 58 | 59 | ``` 60 | usage: unimake.py [-h] [-f FILE] [-d DESTINATION] [target [target ...]] 61 | 62 | positional arguments: 63 | target make target 64 | 65 | optional arguments: 66 | -h, --help show this help message and exit 67 | -f FILE, --file FILE sets the build script 68 | -d DESTINATION, --destination DESTINATION 69 | output directory (base for download and build) 70 | -s config_option_name=value, --set config_option_name=value change a config option 71 | 72 | I'd suggest to use a destination folder that isn't too deep, some dependencies don't handle long paths well. 73 | If the make target is left empty, everything is built. 74 | -------------------------------------------------------------------------------- /unibuild/task.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | 19 | from manager import TaskManager 20 | import os.path 21 | import time 22 | from config import config 23 | 24 | 25 | class Task(object): 26 | """ 27 | base class of all elements in the dependency graph 28 | """ 29 | 30 | class FailBehaviour: 31 | FAIL = 1 32 | CONTINUE = 2 33 | SKIP_PROJECT = 3 34 | 35 | def __init__(self): 36 | self.__dependencies = [] 37 | self._context = None 38 | self.__fail_behaviour = Task.FailBehaviour.FAIL 39 | 40 | @property 41 | def name(self): 42 | return 43 | 44 | @property 45 | def settings(self): 46 | return {} 47 | 48 | @property 49 | def dependencies(self): 50 | return self.__dependencies 51 | 52 | @property 53 | def enabled(self): 54 | return True 55 | 56 | @enabled.setter 57 | def enabled(self, value): 58 | pass 59 | 60 | @property 61 | def fail_behaviour(self): 62 | return self.__fail_behaviour 63 | 64 | def set_fail_behaviour(self, behaviour): 65 | self.__fail_behaviour = behaviour 66 | return self 67 | 68 | @staticmethod 69 | def _expiration(): 70 | return None 71 | 72 | def __success_path(self): 73 | task_name = self.name.replace("/", "_").replace("\\", "_") 74 | ctx_name = self._context.name if self._context else task_name 75 | return os.path.join(config["paths"]["progress"], 76 | "{}_complete_{}.txt".format(ctx_name, task_name)) 77 | 78 | def already_processed(self): 79 | if not os.path.exists(self.__success_path()): 80 | return False 81 | 82 | expiration_duration = self._expiration() 83 | if expiration_duration: 84 | return os.path.getmtime(self.__success_path()) + expiration_duration > time.time() 85 | else: 86 | return True 87 | 88 | def mark_success(self): 89 | with open(self.__success_path(), "w"): 90 | pass 91 | 92 | def depend(self, task): 93 | """ 94 | add a task as a dependency of this one. This means that the dependent task has to be fulfilled 95 | before this one will be processed. 96 | The order in which dependencies are fulfilled is arbitrary however, you can not control which 97 | of two sibling Tasks is processed first. This is because independent tasks could be processed 98 | asynchronously and they may be also be dependencies for a third task. 99 | """ 100 | if type(task) == str: 101 | task_obj = TaskManager().get_task(task) 102 | if task_obj is None: 103 | raise KeyError("unknown project \"{}\"".format(task)) 104 | else: 105 | task = task_obj 106 | else: 107 | if self._context: 108 | task.set_context(self._context) 109 | 110 | self.__dependencies.append(task) 111 | return self 112 | 113 | def set_context(self, context): 114 | if self._context is None: 115 | self._context = context 116 | for dep in self.__dependencies: 117 | dep.set_context(context) 118 | 119 | def applies(self, parameters): 120 | return 121 | 122 | def fulfilled(self): 123 | for dep in self.__dependencies: 124 | if not dep.fulfilled(): 125 | return False 126 | return True 127 | 128 | def prepare(self): 129 | """ 130 | initialize this task. At this point you can rely on required tasks to have run. This should be quick to 131 | complete but needs to initialize everything required by dependent tasks (globals, config, context). 132 | unlike progress, this is called if the task ran successfully already 133 | """ 134 | pass 135 | 136 | def process(self, progress): 137 | pass 138 | -------------------------------------------------------------------------------- /unibuild/projects/boost.py: -------------------------------------------------------------------------------- 1 | from unibuild import Project 2 | from unibuild.modules import b2, sourceforge, Patch, build 3 | from unibuild.projects import python 4 | from config import config 5 | import os 6 | import patch 7 | 8 | 9 | boost_version = config["boost_version"] 10 | python_version = config["python_version"] 11 | vc_version = config['vc_version_for_boost'] 12 | 13 | boost_components = [ 14 | "date_time", 15 | "coroutine", 16 | "filesystem", 17 | "python", 18 | "thread", 19 | "log", 20 | "locale" 21 | ] 22 | 23 | 24 | config_template = ("using python\n" 25 | " : {0}\n" 26 | " : {1}/python.exe\n" 27 | " : {2}/Include\n" 28 | " : {1}\n" 29 | " : {3}\n" 30 | " : BOOST_ALL_NO_LIB=1\n" 31 | " ;") 32 | 33 | 34 | def patchboost(context): 35 | try: 36 | savedpath = os.getcwd() 37 | os.chdir(os.path.join("{}/boost_{}".format(config["paths"]["build"], config["boost_version"].replace(".", "_")))) 38 | pset = patch.fromfile(os.path.join(config["paths"]["build"], "usvfs", "patches", "type_traits_vs15_fix.patch")) 39 | pset.apply() 40 | os.chdir(savedpath) 41 | return True 42 | except OSError: 43 | return False 44 | 45 | Project("boost") \ 46 | .depend(Patch.Copy(os.path.join("{}/boost_{}/stage/lib/boost_python-vc{}-mt-{}.dll" 47 | .format(config["paths"]["build"], 48 | config["boost_version"].replace(".", "_"), 49 | vc_version.replace(".",""), 50 | "_".join(boost_version.split(".")[:-1]) 51 | )), 52 | os.path.join(config["paths"]["install"], "bin")) 53 | .depend(b2.B2(name="Shared").arguments(["address-model={}".format("64" if config['architecture'] == 'x86_64' else "32"), 54 | "-a", 55 | "--user-config={}".format(os.path.join(config['paths']['build'], 56 | "boost_{}".format(boost_version. 57 | replace(".", "_")), 58 | "user-config.jam")), 59 | "-j {}".format(config['num_jobs']), 60 | "toolset=msvc-" + vc_version, 61 | "link=shared", 62 | ] + ["--with-{0}".format(component) for component in boost_components]) 63 | .depend(b2.B2(name="Static").arguments(["address-model={}".format("64" if config['architecture'] == 'x86_64' else "32"), 64 | "-a", 65 | "--user-config={}".format(os.path.join(config['paths']['build'], 66 | "boost_{}".format(boost_version. 67 | replace(".", "_")), "user-config.jam")), 68 | "-j {}".format(config['num_jobs']), 69 | "toolset=msvc-" + vc_version, 70 | "link=static", 71 | "runtime-link=shared", 72 | ] + ["--with-{0}".format(component) for component in boost_components]) 73 | .depend(Patch.CreateFile("user-config.jam", 74 | lambda: config_template.format( 75 | python_version, 76 | os.path.join(python.python['build_path'], "PCBuild", 77 | "{}".format("" if config['architecture'] == 'x86' else "amd64")).replace("\\",'/'), 78 | os.path.join(python.python['build_path']).replace("\\",'/'), 79 | "64" if config['architecture'] == "x86_64" else "32") 80 | ).depend(build.Execute(patchboost) 81 | .depend(sourceforge.Release("boost", 82 | "boost/{0}/boost_{1}.tar.bz2".format(boost_version, 83 | boost_version.replace(".", "_")), 84 | tree_depth=1)) 85 | ).depend("Python") 86 | ) 87 | ) 88 | ) 89 | ) 90 | -------------------------------------------------------------------------------- /unibuild/projects/boostgit.py: -------------------------------------------------------------------------------- 1 | from unibuild import Project, Task 2 | from unibuild.modules import b2, git, Patch, build 3 | from unibuild.projects import python 4 | from config import config 5 | import os 6 | 7 | 8 | boost_version = config["boost_version"] 9 | python_version = config["python_version"] 10 | python_version_minor = config["python_version_minor"] 11 | vc_version = config['vc_version_for_boost'] 12 | python_path = os.path.join(config['paths']['build'], "python-{}{}".format(python_version, python_version_minor)) 13 | 14 | boost_components = [ 15 | "date_time", 16 | "coroutine", 17 | "filesystem", 18 | "python", 19 | "thread", 20 | "log", 21 | "locale" 22 | ] 23 | 24 | 25 | config_template = ("using python\n" 26 | " : {0}\n" 27 | " : {1}/python.exe\n" 28 | " : {2}/Include\n" 29 | " : {1}\n" 30 | " : {3}\n" 31 | " : BOOST_ALL_NO_LIB=1\n" 32 | " ;") 33 | 34 | init_repo = build.Run("git submodule init && git submodule update", name="init boost repository" ,working_directory=lambda: os.path.join(config["paths"]["build"], "boost_git")) \ 35 | .set_fail_behaviour(Task.FailBehaviour.CONTINUE) \ 36 | .depend(git.Clone("https://github.com/boostorg/boost.git", "develop").set_destination("boost_git")) 37 | 38 | Project("boostgit") \ 39 | .depend(b2.B2(name="Shared").arguments(["address-model={}".format("64" if config['architecture'] == 'x86_64' else "32"), 40 | "-a", 41 | "--user-config={}".format(os.path.join(config['paths']['build'], 42 | "boost_git", 43 | "user-config.jam")), 44 | "-j {}".format(config['num_jobs']), 45 | 46 | "toolset=msvc-" + vc_version, 47 | 48 | "link=shared", 49 | "include={}".format(os.path.join(config['paths']['build'], "icu", "dist", "include", "unicode")), 50 | "-sICU_PATH={}".format( 51 | os.path.join(config['paths']['build'], "icu", "dist")), 52 | "-sHAVE_ICU=1", 53 | ] + ["--with-{0}".format(component) for component in boost_components]) 54 | .depend(b2.B2(name="Static").arguments(["address-model={}".format("64" if config['architecture'] == 'x86_64' else "32"), 55 | "-a", 56 | "--user-config={}".format(os.path.join(config['paths']['build'], 57 | "boost_git", "user-config.jam")), 58 | "-j {}".format(config['num_jobs']), 59 | 60 | "toolset=msvc-" + vc_version, 61 | 62 | "link=static", 63 | "runtime-link=shared", 64 | "include={}".format(os.path.join(config['paths']['build'], "icu", "dist", "include", "unicode")), 65 | "-sICU_PATH={}".format(os.path.join(config['paths']['build'], "icu", "dist")), 66 | "-sHAVE_ICU=1", 67 | ] + ["--with-{0}".format(component) for component in boost_components]) 68 | .depend(build.Run(r"bootstrap.bat",working_directory=lambda: os.path.join(config["paths"]["build"], "boost_git"))) 69 | .depend(Patch.CreateFile("user-config.jam", 70 | lambda: config_template.format( 71 | python_version, 72 | os.path.join(python_path, "PCBuild", 73 | "{}".format("" if config['architecture'] == 'x86' else "amd64")).replace("\\",'/'), 74 | python_path, 75 | "64" if config['architecture'] == "x86_64" else "32") 76 | )) 77 | .depend(init_repo) 78 | # .depend(sourceforge.Release("boost", 79 | # "boost/{0}/boost_{1}.tar.bz2".format(boost_version, 80 | # boost_version.replace(".", "_")), 81 | # tree_depth=1)) 82 | ).depend("icu").depend("Python") 83 | ) 84 | 85 | -------------------------------------------------------------------------------- /unibuild/projects/python.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | 19 | from unibuild.project import Project 20 | from unibuild.modules import github, msbuild, build, urldownload 21 | from config import config 22 | from unimake import get_visual_studio_2017_or_more 23 | import os 24 | import shutil 25 | from glob import glob 26 | import errno 27 | 28 | 29 | python_version = config.get('python_version', "2.7") + config.get('python_version_minor', ".12") 30 | python_toolset = config.get('vc_platformtoolset', "v140") 31 | python_url = "https://www.python.org/ftp/python" 32 | 33 | def make_sure_path_exists(path): 34 | try: 35 | os.makedirs(path) 36 | except OSError as exception: 37 | if exception.errno != errno.EEXIST: 38 | raise 39 | 40 | 41 | def python_environment(): 42 | result = config['__environment'].copy() 43 | result['Path'] += ";" + os.path.dirname(config['paths']['svn']) 44 | return result 45 | 46 | 47 | def upgrade_args(): 48 | env = config['__environment'] 49 | devenv_path = os.path.join(config['paths']['visual_studio_basedir'], "Common7", "IDE") 50 | #MSVC2017 supports building with the MSVC2015 toolset though this will break here, Small work around to make sure devenv.exe exists 51 | #If not try MSVC2017 instead 52 | res = os.path.isfile(os.path.join(devenv_path, "devenv.exe")) 53 | if res: 54 | return [os.path.join(devenv_path, "devenv.exe"), 55 | "PCBuild/pcbuild.sln", 56 | "/upgrade"] 57 | else: 58 | return [os.path.join(get_visual_studio_2017_or_more('15.0'),"..","..","..","Common7", "IDE", "devenv.exe"), 59 | "PCBuild/pcbuild.sln", 60 | "/upgrade"] 61 | 62 | #if config.get('prefer_binary_dependencies', False): 63 | if False: 64 | # the python installer registers in windows and prevent further installations. This means this installation 65 | # would interfere with the rest of the system 66 | filename = "python-{0}{1}.msi".format( 67 | python_version, 68 | ".amd64" if config['architecture'] == "x86_64" else "" 69 | ) 70 | 71 | python = Project("Python") \ 72 | .depend(build.Run("msiexec /i {0} TARGETDIR={1} /qn ADDLOCAL=DefaultFeature,SharedCRT" 73 | .format(os.path.join(config['paths']['download'], filename), 74 | os.path.join(config['paths']['build'], "python-{}".format(python_version)) 75 | ) 76 | ) 77 | .depend(urldownload.URLDownload("{0}/{1}/{2}" 78 | .format(python_url, 79 | python_version, 80 | filename 81 | ) 82 | ) 83 | ) 84 | ) 85 | else: 86 | def install(context): 87 | make_sure_path_exists(os.path.join(config["paths"]["install"], "libs")) 88 | path_segments = [context['build_path'], "PCbuild"] 89 | if config['architecture'] == "x86_64": 90 | path_segments.append("amd64") 91 | path_segments.append("*.lib") 92 | shutil.copy(os.path.join(python['build_path'],"PC", "pyconfig.h"),os.path.join(python['build_path'], "Include","pyconfig.h")) 93 | for f in glob(os.path.join(*path_segments)): 94 | shutil.copy(f, os.path.join(config["paths"]["install"], "libs")) 95 | return True 96 | 97 | python = Project("Python") \ 98 | .depend(build.Execute(install) 99 | .depend(msbuild.MSBuild("PCBuild/PCBuild.sln", "python,pyexpat", 100 | project_PlatformToolset=python_toolset) 101 | # .depend(build.Run(r'PCBuild\\build.bat -e -c Release -m -p {} "/p:PlatformToolset={}"'.format("x64" if config['architecture'] == 'x86_64' else "x86",config['vc_platform']), 102 | # environment=python_environment(), 103 | # working_directory=lambda: os.path.join(python['build_path'])) 104 | .depend(build.Run(upgrade_args, name="upgrade python project") 105 | .depend(github.Source("LePresidente", "cpython", config.get('python_version', "2.7"))\ 106 | .set_destination("python-{}".format(python_version)))) 107 | ) 108 | ) 109 | -------------------------------------------------------------------------------- /unibuild/projects/icu.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | from unibuild import Project 19 | from unibuild.modules import build, sourceforge, Patch 20 | from config import config 21 | import subprocess 22 | import os 23 | import logging 24 | 25 | icu_version = config['icu_version'] 26 | icu_version_minor = config['icu_version_minor'] 27 | 28 | # installation happens concurrently in separate process. We need to wait for all relevant files to exist, 29 | # and can determine failure only by timeout 30 | timeout = 15 # seconds 31 | 32 | 33 | def icu_environment(): 34 | s = "" 35 | MSVCFolders = ("Microsoft Visual Studio","MSBuild","Framework","Windows Kits","Microsoft SDK", "HTML Help") 36 | result = config['__environment'].copy() 37 | a = result['PATH'] 38 | for x in a.split(";"): 39 | if any(word in x for word in MSVCFolders): 40 | if s: 41 | s += x + ";" 42 | else: 43 | s = x + ";" 44 | else: 45 | if "cygwin" not in x: 46 | s +=os.path.join(config['paths']['build'], "cygwin", "bin") + ";" + x + ";" 47 | else: 48 | s += x + ";" 49 | result['PATH'] = s 50 | return result 51 | 52 | 53 | # Warning, build_run only works for me if cygwin is first after VS in the path (as requested in readme) 54 | # So I change my path before calling unimake.py 55 | build_icu = build.Run("make && make install".format(os.path.join(config['paths']['build'], "cygwin", "bin")), 56 | name="ICU Make", 57 | environment=icu_environment(), 58 | working_directory=lambda: os.path.join(config["paths"]["build"], "icu", "source")) 59 | 60 | 61 | # Warning this won't work if there are Embarcadero compiler definition in your path 62 | class ConfigureIcu(build.Builder): 63 | def __init__(self): 64 | super(ConfigureIcu, self).__init__() 65 | 66 | @property 67 | def name(self): 68 | return "icu configure" 69 | 70 | def process(self, progress): 71 | from distutils.spawn import find_executable 72 | res = find_executable("cygpath", os.path.join(config['paths']['build'], "cygwin", "bin")) 73 | if res is not None: 74 | current_dir_cygwin = subprocess.check_output("{0} {1}" 75 | .format(res, 76 | os.path.join(config["paths"]["build"], "icu", "dist"))) 77 | 78 | soutpath = os.path.join(self._context["build_path"], "stdout.log") 79 | serrpath = os.path.join(self._context["build_path"], "stderr.log") 80 | with open(soutpath, "w") as sout: 81 | with open(serrpath, "w") as serr: 82 | res = find_executable("bash", os.path.join(config['paths']['build'], "cygwin", "bin")) 83 | proc = subprocess.Popen([res, "runConfigureICU", "Cygwin/MSVC", "--prefix" 84 | , "{}".format(current_dir_cygwin)], 85 | env=icu_environment(), 86 | cwd=os.path.join(self._context["build_path"], "source"), 87 | shell=True, 88 | stdout=sout, stderr=serr) 89 | proc.communicate() 90 | if proc.returncode != 0: 91 | logging.error("failed to run icu runConfigureICU (returncode %s), see %s and %s", 92 | proc.returncode, soutpath, serrpath) 93 | return False 94 | 95 | return True 96 | 97 | 98 | Convert_icu = build.Run(r"dos2unix -f configure", 99 | environment=icu_environment(), 100 | working_directory=lambda: os.path.join(config["paths"]["build"], "icu", "source")) 101 | 102 | icu = Project('icu') \ 103 | .depend(build_icu 104 | .depend(ConfigureIcu() 105 | .depend(Convert_icu 106 | .depend(Patch.Replace("source/io/ufile.c", 107 | "#if U_PLATFORM_USES_ONLY_WIN32_API", 108 | "#if U_PLATFORM_USES_ONLY_WIN32_API && _MSC_VER < 1900") 109 | .depend(sourceforge.Release("icu","ICU4C/{0}.{1}/icu4c-{0}_{1}-src.tgz" 110 | .format(icu_version,icu_version_minor),tree_depth=1) 111 | .set_destination("icu") 112 | ) 113 | ) 114 | ) 115 | ) 116 | )\ 117 | .depend("cygwin") 118 | -------------------------------------------------------------------------------- /unibuild/modules/git.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | 19 | from subprocess import Popen 20 | from config import config 21 | from repository import Repository 22 | import os 23 | import logging 24 | from unibuild import Task 25 | from urlparse import urlparse, urlsplit 26 | 27 | 28 | class SuperRepository(Task): 29 | def __init__(self, name): 30 | super(SuperRepository, self).__init__() 31 | self.__name = name 32 | self.__context_data = {} 33 | self.prepare() 34 | 35 | def prepare(self): 36 | self.__context_data['build_path'] = os.path.join(config["paths"]["build"], self.__name) 37 | 38 | @property 39 | def path(self): 40 | return self.__context_data['build_path'] 41 | 42 | @property 43 | def name(self): 44 | return self.__name 45 | 46 | def __getitem__(self, key): 47 | return self.__context_data[key] 48 | 49 | def __setitem__(self, key, value): 50 | self.__context_data[key] = value 51 | 52 | def __contains__(self, keys): 53 | return self.__context_data.__contains__(keys) 54 | 55 | def process(self, progress): 56 | if not os.path.isdir(self.path): 57 | os.makedirs(self.path) 58 | proc = Popen([config['paths']['git'], "init"], 59 | cwd=self.path, 60 | env=config['__environment']) 61 | proc.communicate() 62 | if proc.returncode != 0: 63 | logging.error("failed to init superproject %s (returncode %s)", self._name, proc.returncode) 64 | return False 65 | 66 | return True 67 | 68 | 69 | class Clone(Repository): 70 | def __init__(self, url, branch, super_repository=None, update=True, commit=None): 71 | super(Clone, self).__init__(url, branch) 72 | 73 | self.__super_repository = super_repository 74 | self.__base_name = os.path.basename(self._url) 75 | self.__update = update 76 | self.__commit = commit 77 | if self.__super_repository is not None: 78 | self._output_file_path = os.path.join(self.__super_repository.path, self.__determine_name()) 79 | self.depend(super_repository) 80 | 81 | def __determine_name(self): 82 | return self.__base_name 83 | 84 | def prepare(self): 85 | self._context['build_path'] = self._output_file_path 86 | 87 | def process(self, progress): 88 | proc = None 89 | if os.path.exists(os.path.join(self._output_file_path, ".git")): 90 | if self.__update and not config.get('offline', False): 91 | proc = Popen([config['paths']['git'], "pull", self._url, self._branch], 92 | cwd=self._output_file_path, 93 | env=config["__environment"]) 94 | else: 95 | if self.__super_repository is not None: 96 | proc = Popen([config['paths']['git'], "submodule", "add", "-b", self._branch, 97 | "--force", "--name", self.__base_name, 98 | self._url, self.__base_name 99 | ], 100 | cwd=self.__super_repository.path, 101 | env=config['__environment']) 102 | else: 103 | proc = Popen([config['paths']['git'], "clone", "-b", self._branch, 104 | self._url, self._context["build_path"]], 105 | env=config["__environment"]) 106 | 107 | if proc is not None: 108 | proc.communicate() 109 | if proc.returncode != 0: 110 | logging.error("failed to clone repository %s (returncode %s)", self._url, proc.returncode) 111 | return False 112 | 113 | if self.__commit is not None: 114 | proc = Popen([config['paths']['git'], "checkout", self.__commit], 115 | cwd = self._context["build_path"], 116 | env=config["__environment"]) 117 | 118 | if proc is not None: 119 | proc.communicate() 120 | if proc.returncode != 0: 121 | logging.error("failed to checkout repository %s (returncode %s)", self._url, proc.returncode) 122 | return False 123 | 124 | return True 125 | 126 | @staticmethod 127 | def _expiration(): 128 | return config.get('repo_update_frequency', 60 * 60 * 24) # default: one day 129 | 130 | def set_destination(self, destination_name): 131 | self.__base_name = destination_name.replace("/", os.path.sep) 132 | if self.__super_repository is not None: 133 | self._output_file_path = os.path.join(self.__super_repository.path, self.__base_name) 134 | else: 135 | self._output_file_path = os.path.join(config["paths"]["build"], self.__base_name) 136 | return self 137 | -------------------------------------------------------------------------------- /unibuild/projects/pyqt5.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | 19 | from unibuild.modules import sourceforge, build, Patch 20 | from unibuild.utility import lazy 21 | from unibuild.utility.lazy import doclambda 22 | from unibuild import Project 23 | from config import config 24 | from subprocess import Popen 25 | from glob import glob 26 | import errno 27 | import shutil 28 | import os 29 | import logging 30 | 31 | import qt5 # import to get at qt version information 32 | import sip 33 | import python 34 | 35 | icu_version = config['icu_version'] 36 | 37 | def make_sure_path_exists(path): 38 | try: 39 | os.makedirs(path) 40 | except OSError as exception: 41 | if exception.errno != errno.EEXIST: 42 | raise 43 | 44 | 45 | def pyqt5_env(): 46 | res = config['__environment'].copy() 47 | res['path'] = ";".join([ 48 | os.path.join(config['paths']['build'], "qt5", "bin"), 49 | os.path.join(config['paths']['build'], "sip-{}".format(sip.sip_version), "sipgen"), 50 | ]) + ";" + res['path'] 51 | res['LIB'] = os.path.join(config["__build_base_path"], "install", "libs") + ";" + res['LIB'] 52 | res['pythonhome'] = python.python['build_path'] 53 | return res 54 | 55 | 56 | def copy_pyd(context): 57 | make_sure_path_exists(os.path.join(config["__build_base_path"], "install", "bin", "plugins", "data", "PyQt5")) 58 | srcdir = os.path.join(python.python['build_path'], "Lib", "site-packages", "PyQt5") 59 | dstdir = os.path.join(config["__build_base_path"], "install", "bin", "plugins", "data", "PyQt5") 60 | shutil.copy(os.path.join(srcdir, "__init__.py"),dstdir) 61 | shutil.copy(os.path.join(srcdir, "QtCore.pyd"), dstdir) 62 | shutil.copy(os.path.join(srcdir, "QtGui.pyd"),dstdir) 63 | shutil.copy(os.path.join(srcdir, "QtWidgets.pyd"), dstdir) 64 | return True 65 | 66 | 67 | class PyQt5Configure(build.Builder): 68 | def __init__(self): 69 | super(PyQt5Configure, self).__init__() 70 | 71 | @property 72 | def name(self): 73 | return "pyqt configure" 74 | 75 | def process(self, progress): 76 | soutpath = os.path.join(self._context["build_path"], "stdout.log") 77 | serrpath = os.path.join(self._context["build_path"], "stderr.log") 78 | with open(soutpath, "w") as sout: 79 | with open(serrpath, "w") as serr: 80 | bp = python.python['build_path'] 81 | 82 | proc = Popen([os.path.join(python.python['build_path'],"PCbuild","amd64","python.exe"), "configure.py", "--confirm-license", 83 | "-b", bp, 84 | "-d", os.path.join(bp, "Lib", "site-packages"), 85 | "-v", os.path.join(bp, "sip", "PyQt5"), 86 | "--sip-incdir", os.path.join(bp, "Include"), 87 | "--spec=win32-msvc"], 88 | env=pyqt5_env(), 89 | cwd=self._context["build_path"], 90 | shell=True, 91 | stdout=sout, stderr=serr) 92 | proc.communicate() 93 | if proc.returncode != 0: 94 | logging.error("failed to run pyqt configure.py (returncode %s), see %s and %s", 95 | proc.returncode, soutpath, serrpath) 96 | return False 97 | 98 | return True 99 | 100 | 101 | Project("PyQt5") \ 102 | .depend(build.Execute(copy_pyd) 103 | .depend(Patch.Copy([os.path.join(qt5.qt_inst_path, "bin", "Qt5Core.dll"), 104 | os.path.join(qt5.qt_inst_path, "bin", "Qt5Xml.dll"), 105 | os.path.join(config['paths']['build'], "icu" , "dist", "lib", "icudt{}.dll".format(icu_version)), 106 | os.path.join(config['paths']['build'], "icu", "dist", "lib", "icuin{}.dll".format(icu_version)), 107 | os.path.join(config['paths']['build'], "icu", "dist", "lib", "icuuc{}.dll".format(icu_version))], 108 | doclambda(lambda: python.python['build_path'], "python path")) 109 | .depend(build.Make(environment=lazy.Evaluate(pyqt5_env)).install() 110 | .depend(PyQt5Configure() 111 | .depend("sip") 112 | .depend("Qt5") 113 | .depend(sourceforge.Release("pyqt", 114 | "PyQt5/PyQt-{0}.{1}/PyQt5_gpl-{0}.{1}.zip" 115 | .format(qt5.qt_version, qt5.qt_version_minor), 116 | tree_depth=1)) 117 | ) 118 | ) 119 | ) 120 | ) 121 | 122 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | 19 | from _winreg import * 20 | from unibuild.utility.lazy import Lazy 21 | import os 22 | import multiprocessing 23 | 24 | global missing_prerequisites 25 | missing_prerequisites = False 26 | 27 | 28 | def path_or_default(filename, *default): 29 | from distutils.spawn import find_executable 30 | defaults = gen_search_folders(*default) 31 | res = find_executable(filename, os.environ['PATH'] + ";" + ";".join(defaults)) 32 | if res is None: 33 | print 'Cannot find', filename, 'on your path or in', os.path.join('', *default) 34 | global missing_prerequisites 35 | missing_prerequisites = True 36 | return res 37 | 38 | 39 | def get_from_hklm(path, name, wow64=False): 40 | flags = KEY_READ 41 | if wow64: 42 | flags |= KEY_WOW64_32KEY 43 | 44 | # avoids crashing if a product is not present 45 | try: 46 | with OpenKey(HKEY_LOCAL_MACHINE, path, 0, flags) as key: 47 | return QueryValueEx(key, name)[0] 48 | except: 49 | return "" 50 | 51 | # To detect the editon of VS installed as of VS 2017 52 | vs_editions = [ 53 | "enterprise", 54 | "professional", 55 | "community", 56 | ] 57 | 58 | 59 | program_files_folders = [ 60 | os.environ['ProgramFiles(x86)'], 61 | os.environ['ProgramFiles'], 62 | os.environ['ProgramW6432'], 63 | "C:\\", 64 | "D:\\" 65 | ] 66 | 67 | 68 | def gen_search_folders(*subpath): 69 | return [ 70 | os.path.join(search_folder, *subpath) 71 | for search_folder in program_files_folders 72 | ] 73 | 74 | config = { 75 | 'tools': { 76 | 'make': "nmake", 77 | }, 78 | 'architecture': 'x86_64', # Don't change this as we spawn the usvfs x86 build later on. 79 | 'vc_version': '14.0', 80 | 'vc_platformtoolset': 'v140', 81 | 'vc_CustomInstallPath': '', # If you installed VC to a custom location put the full path here 82 | # eg. E:\Microsoft Visual Studio 14.0 83 | 'build_type': "RelWithDebInfo", 84 | 'offline': False, # if set, non-mandatory network requests won't be made. 85 | # This is stuff like updating source repositories. The initial 86 | # download of course can't be surpressed. 87 | 'prefer_binary_dependencies': False, # currently non-functional 88 | 'optimize': False, # activate link-time code generation and other optimization. 89 | # This massively increases build time but produces smaller 90 | # binaries and marginally faster code 91 | 'Installer': True, # Used to create installer at end of build, Forces everything to be built 92 | 'repo_update_frequency': 60 * 60 * 24, # in seconds 93 | 'num_jobs': multiprocessing.cpu_count() + 1, 94 | 95 | 'Main_Author': 'LePresidente', # the current maintainer 96 | 'Distrib_Author': 'TanninOne', # the current distribution (and the original Author) 97 | 'Work_Author': 'Hugues92', # yourself 98 | 99 | 'qt_version': '5.8', # currently evolving 100 | 'openssl_version': '1.0.2k', # changes often, so better to edit here 101 | 'zlib_version': '1.2.11', # changes often, so better to edit here 102 | 'grep_version': '2.5.4', # moved here as commented in qt5.py 103 | 'boost_version': '1.64.0', # for -DBOOST_ROOT, also, it is either to change from here 104 | 'vc_version_for_boost': '14.0', # boost 1.63 does not support VS 2017 yet 105 | 'python_version': '2.7', # used below and in python.py 106 | 'python_version_minor': '.13', # used in python.py 107 | 'icu_version': '58', # used in PyQt5 108 | 'icu_version_minor': '2', # for consistency 109 | 'WixToolSet_Version_Binary': '311', # Wix Binary Version 110 | } 111 | 112 | config['paths'] = { 113 | 'download': "{base_dir}\\downloads", 114 | 'build': "{base_dir}\\{build_dir}", 115 | 'progress': "{base_dir}\\{progress_dir}", 116 | 'install': "{base_dir}\\{install_dir}", 117 | # 'graphviz': path_or_default("dot.exe", "Graphviz2.38", "bin"), 118 | 'cmake': path_or_default("cmake.exe", "CMake", "bin"), 119 | 'git': path_or_default("git.exe", "Git", "bin"), 120 | 'perl': path_or_default("perl.exe", "StrawberryPerl", "bin"), 121 | 'ruby': path_or_default("ruby.exe", "Ruby22-x64", "bin"), 122 | 'svn': path_or_default("svn.exe", "SlikSvn", "bin"), 123 | '7z': path_or_default("7z.exe", "7-Zip"), 124 | # we need a python that matches the build architecture 125 | 'python': Lazy(lambda: os.path.join(get_from_hklm(r"SOFTWARE\Python\PythonCore\{}\InstallPath".format(config['python_version']), 126 | "", config['architecture'] == "x86"), 127 | "python.exe")), 128 | 'visual_studio_base': "", 129 | 'visual_studio': "" # will be set in unimake.py after args are evaluated 130 | } 131 | 132 | if missing_prerequisites: 133 | print '\nMissing prerequisites listed above - cannot continue' 134 | exit(1) 135 | -------------------------------------------------------------------------------- /libpatterns.py: -------------------------------------------------------------------------------- 1 | patterns = [ 2 | "Lib/BaseHTTPServer.py", 3 | "Lib/Bastion.py", 4 | "Lib/CGIHTTPServer.py", 5 | "Lib/ConfigParser.py", 6 | "Lib/ConfigParser.pyo", 7 | "Lib/Cookie.py", 8 | "Lib/DocXMLRPCServer.py", 9 | "Lib/HTMLParser.py", 10 | "Lib/MimeWriter.py", 11 | "Lib/Queue.py", 12 | "Lib/SimpleHTTPServer.py", 13 | "Lib/SimpleXM", 14 | "Lib/SocketServer.py", 15 | "Lib/StringIO.py", 16 | "Lib/UserList.py", 17 | "Lib/UserDict.py", 18 | "Lib/UserDict.pyo", 19 | "Lib/UserString.py", 20 | "Lib/_LWPCookieJar.py", 21 | "Lib/_MozillaCookieJar.py", 22 | "Lib/__future__.py", 23 | "Lib/_abcoll.py", 24 | "Lib/_abcoll.pyo", 25 | "Lib/_pyio.py", 26 | "Lib/_strptime.py", 27 | "Lib/_threading_local.py", 28 | "Lib/_weakrefset.py", 29 | "Lib/_weakrefset.pyo", 30 | "Lib/abc.py", 31 | "Lib/abc.pyo", 32 | "Lib/aifc.py", 33 | "Lib/antigravity.py", 34 | "Lib/anydbm.py", 35 | "Lib/argparse.py", 36 | "Lib/ast.py", 37 | "Lib/asynchat.py", 38 | "Lib/asyncore.py", 39 | "Lib/atexit.py", 40 | "Lib/atexit.pyo", 41 | "Lib/audiodev.py", 42 | "Lib/base64.py", 43 | "Lib/bdb.py", 44 | "Lib/binhex.py", 45 | "Lib/bisect.py", 46 | "Lib/cProfile.py", 47 | "Lib/calendar.py", 48 | "Lib/cgi.py", 49 | "Lib/cgitb.py", 50 | "Lib/chunk.py", 51 | "Lib/cmd.py", 52 | "Lib/code.py", 53 | "Lib/codecs.py", 54 | "Lib/codecs.pyo", 55 | "Lib/codeop.py", 56 | "Lib/collections.py", 57 | "Lib/collections.pyo", 58 | "Lib/colorsys.py", 59 | "Lib/commands.py", 60 | "Lib/compileall.py", 61 | "Lib/contextlib.py", 62 | "Lib/cookielib.py", 63 | "Lib/copy.py", 64 | "Lib/copy_reg.py", 65 | "Lib/copy_reg.pyo", 66 | "Lib/csv.py", 67 | "Lib/dbhash.py", 68 | "Lib/decimal.py", 69 | "Lib/difflib.py", 70 | "Lib/dircache.py", 71 | "Lib/dis.py", 72 | "Lib/doctest.py", 73 | "Lib/dumbdbm.py", 74 | "Lib/dummy_thread.py", 75 | "Lib/dummy_threading.py", 76 | "Lib/filecmp.py", 77 | "Lib/fileinput.py", 78 | "Lib/fnmatch.py", 79 | "Lib/formatter.py", 80 | "Lib/fpformat.py", 81 | "Lib/fractions.py", 82 | "Lib/ftplib.py", 83 | "Lib/functools.py", 84 | "Lib/functools.pyo", 85 | "Lib/genericpath.py", 86 | "Lib/genericpath.pyo", 87 | "Lib/getopt.py", 88 | "Lib/getpass.py", 89 | "Lib/gettext.py", 90 | "Lib/glob.py", 91 | "Lib/gzip.py", 92 | "Lib/hashlib.py", 93 | "Lib/heapq.py", 94 | "Lib/heapq.pyo", 95 | "Lib/hmac.py", 96 | "Lib/htmlentitydefs.py", 97 | "Lib/htmllib.py", 98 | "Lib/httplib.py", 99 | "Lib/ihooks.py", 100 | "Lib/imaplib.py", 101 | "Lib/imghdr.py", 102 | "Lib/imputil.py", 103 | "Lib/inspect.py", 104 | "Lib/io.py", 105 | "Lib/keyword.py", 106 | "Lib/keyword.pyo", 107 | "Lib/linecache.py", 108 | "Lib/linecache.pyo", 109 | "Lib/locale.py", 110 | "Lib/locale.pyo", 111 | "Lib/macpath.py", 112 | "Lib/macurl2path.py", 113 | "Lib/mailbox.py", 114 | "Lib/mailcap.py", 115 | "Lib/markupbase.py", 116 | "Lib/md5.py", 117 | "Lib/mhlib.py", 118 | "Lib/mimetools.py", 119 | "Lib/mimetypes.py", 120 | "Lib/mimify.py", 121 | "Lib/modulefinder.py", 122 | "Lib/multifile.py", 123 | "Lib/mutex.py", 124 | "Lib/netrc.py", 125 | "Lib/new.py", 126 | "Lib/nntplib.py", 127 | "Lib/ntpath.py", 128 | "Lib/ntpath.pyo", 129 | "Lib/nturl2path.py", 130 | "Lib/numbers.py", 131 | "Lib/opcode.py", 132 | "Lib/optparse.py", 133 | "Lib/os.py", 134 | "Lib/os.pyo", 135 | "Lib/os2emxpath.py", 136 | "Lib/pdb.py", 137 | "Lib/pickle.py", 138 | "Lib/pickletools.py", 139 | "Lib/pipes.py", 140 | "Lib/pkgutil.py", 141 | "Lib/platform.py", 142 | "Lib/plistlib.py", 143 | "Lib/popen2.py", 144 | "Lib/poplib.py", 145 | "Lib/posixfile.py", 146 | "Lib/posixpath.py", 147 | "Lib/pprint.py", 148 | "Lib/profile.py", 149 | "Lib/pstats.py", 150 | "Lib/pty.py", 151 | "Lib/py_compile.py", 152 | "Lib/pyclbr.py", 153 | "Lib/quopri.py", 154 | "Lib/random.py", 155 | "Lib/re.py", 156 | "Lib/re.pyo", 157 | "Lib/repr.py", 158 | "Lib/rexec.py", 159 | "Lib/rfc822.py", 160 | "Lib/rlcompleter.py", 161 | "Lib/robotparser.py", 162 | "Lib/runpy.py", 163 | "Lib/sched.py", 164 | "Lib/sets.py", 165 | "Lib/sgmllib.py", 166 | "Lib/sha.py", 167 | "Lib/shelve.py", 168 | "Lib/shlex.py", 169 | "Lib/shutil.py", 170 | "Lib/sip.pyd", 171 | "Lib/site.py", 172 | "Lib/site.pyo", 173 | "Lib/smtpd.py", 174 | "Lib/smtplib.py", 175 | "Lib/sndhdr.py", 176 | "Lib/socket.py", 177 | "Lib/sre.py", 178 | "Lib/sre_compile.py", 179 | "Lib/sre_compile.pyo", 180 | "Lib/sre_constants.py", 181 | "Lib/sre_constants.pyo", 182 | "Lib/sre_parse.py", 183 | "Lib/sre_parse.pyo", 184 | "Lib/ssl.py", 185 | "Lib/stat.py", 186 | "Lib/stat.pyo", 187 | "Lib/statvfs.py", 188 | "Lib/string.py", 189 | "Lib/stringold.py", 190 | "Lib/stringprep.py", 191 | "Lib/struct.py", 192 | "Lib/struct.pyo", 193 | "Lib/subprocess.py", 194 | "Lib/sunau.py", 195 | "Lib/sunaudio.py", 196 | "Lib/symbol.py", 197 | "Lib/symtable.py", 198 | "Lib/sysconfig.py", 199 | "Lib/sysconfig.pyo", 200 | "Lib/tabnanny.py", 201 | "Lib/tarfile.py", 202 | "Lib/telnetlib.py", 203 | "Lib/tempfile.py", 204 | "Lib/textwrap.py", 205 | "Lib/this.py", 206 | "Lib/threading.py", 207 | "Lib/timeit.py", 208 | "Lib/toaiff.py", 209 | "Lib/token.py", 210 | "Lib/tokenize.py", 211 | "Lib/trace.py", 212 | "Lib/traceback.py", 213 | "Lib/traceback.pyo", 214 | "Lib/tty.py", 215 | "Lib/types.py", 216 | "Lib/types.pyo", 217 | "Lib/urllib.py", 218 | "Lib/urllib2.py", 219 | "Lib/urlparse.py", 220 | "Lib/user.py", 221 | "Lib/uu.py", 222 | "Lib/uuid.py", 223 | "Lib/warnings.py", 224 | "Lib/warnings.pyo", 225 | "Lib/wave.py", 226 | "Lib/weakref.py", 227 | "Lib/webbrowser.py", 228 | "Lib/whichdb.py", 229 | "Lib/xdrlib.py", 230 | "Lib/xmllib.py", 231 | "Lib/xmlrpclib.py", 232 | "Lib/zipfile.py", 233 | 234 | "Lib/ctypes/*", 235 | "Lib/ctypes/macholib/*", 236 | "Lib/email/*", 237 | "Lib/email/mime/*", 238 | "Lib/encodings/*", 239 | "Lib/importlib/*", 240 | "Lib/json/*", 241 | "Lib/logging/*", 242 | "Lib/multiprocessing/*", 243 | "Lib/multiprocessing/dummy/*", 244 | "Lib/sqlite3/*", 245 | "Lib/wsgiref/*", 246 | "Lib/xml/__init__.py", 247 | "Lib/xml/dom/*", 248 | "Lib/xml/etree/*", 249 | "Lib/xml/parsers/*", 250 | "Lib/xml/sax/*", 251 | ] 252 | -------------------------------------------------------------------------------- /unibuild/modules/urldownload.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | 19 | from unibuild.retrieval import Retrieval 20 | from config import config 21 | import os 22 | import sys 23 | import logging 24 | from urlparse import urlparse 25 | import urllib2 26 | import tarfile 27 | import zipfile 28 | import subprocess 29 | import shutil 30 | from unibuild.utility import ProgressFile 31 | from unibuild.utility.context_objects import on_failure 32 | 33 | 34 | class URLDownload(Retrieval): 35 | 36 | BLOCK_SIZE = 8192 37 | 38 | def __init__(self, url, tree_depth=0): 39 | super(URLDownload, self).__init__() 40 | self.__url = url 41 | self.__tree_depth = tree_depth 42 | self.__file_name = os.path.basename(urlparse(self.__url).path) 43 | 44 | @property 45 | def name(self): 46 | return "download {0}".format(self.__file_name) 47 | 48 | def set_destination(self, destination_name): 49 | name, ext = os.path.splitext(self.__file_name) 50 | if name.lower().endswith(".tar"): 51 | ext = ".tar" + ext 52 | 53 | self.__file_name = destination_name + ext 54 | return self 55 | 56 | def prepare(self): 57 | name, ext = os.path.splitext(self.__file_name) 58 | if name.lower().endswith(".tar"): 59 | name, e2 = os.path.splitext(name) 60 | 61 | if 'build_path' not in self._context: 62 | output_file_path = os.path.join(config['paths']['build'], name) 63 | self._context['build_path'] = output_file_path 64 | 65 | def process(self, progress): 66 | logging.info("processing download") 67 | output_file_path = self._context['build_path'] 68 | archive_file_path = os.path.join(config['paths']['download'], self.__file_name) 69 | 70 | if os.path.isfile(output_file_path): 71 | logging.info("File already extracted: {0}".format(archive_file_path)) 72 | else: 73 | if os.path.isfile(archive_file_path): 74 | logging.info("File already downloaded: {0}".format(archive_file_path)) 75 | else: 76 | logging.info("File not yet downloaded: {0}".format(archive_file_path)) 77 | self.download(archive_file_path, progress) 78 | progress.finish() 79 | 80 | if not self.extract(archive_file_path, output_file_path, progress): 81 | return False 82 | progress.finish() 83 | 84 | builddir = os.listdir(self._context["build_path"]) 85 | if len(builddir) == 1: 86 | self._context["build_path"] = os.path.join(self._context["build_path"], builddir[0]) 87 | return True 88 | 89 | def download(self, output_file_path, progress): 90 | logging.info("Downloading {} to {}".format(self.__url, output_file_path)) 91 | progress.job = "Downloading" 92 | data = urllib2.urlopen(self.__url) 93 | with open(output_file_path, 'wb') as outfile: 94 | meta = data.info() 95 | length_str = meta.getheaders("Content-Length") 96 | if length_str: 97 | progress.maximum = int(length_str[0]) 98 | else: 99 | progress.maximum = sys.maxint 100 | 101 | bytes_read = 0 102 | while True: 103 | block = data.read(URLDownload.BLOCK_SIZE) 104 | if not block: 105 | break 106 | bytes_read += len(block) 107 | outfile.write(block) 108 | progress.value = bytes_read 109 | 110 | def extract(self, archive_file_path, output_file_path, progress): 111 | def progress_func(pos, size): 112 | progress.value = int(pos * 100 / size) 113 | 114 | logging.info("Extracting {0}".format(self.__url)) 115 | 116 | progress.value = 0 117 | progress.job = "Extracting" 118 | output_file_path = u"\\\\?\\" + os.path.abspath(output_file_path) 119 | 120 | try: 121 | os.makedirs(output_file_path) 122 | except Exception: 123 | # doesn't matter if the directory already exists. 124 | pass 125 | 126 | with on_failure(lambda: shutil.rmtree(output_file_path)): 127 | filename, extension = os.path.splitext(self.__file_name) 128 | if extension == ".gz" or extension == ".tgz": 129 | archive_file = ProgressFile(archive_file_path, progress_func) 130 | with tarfile.open(fileobj=archive_file, mode='r:gz') as arch: 131 | arch.extractall(output_file_path) 132 | archive_file.close() 133 | elif extension == ".bz2": 134 | archive_file = ProgressFile(archive_file_path, progress_func) 135 | with tarfile.open(fileobj=archive_file, mode='r:bz2') as arch: 136 | arch.extractall(output_file_path) 137 | archive_file.close() 138 | elif extension == ".zip": 139 | archive_file = ProgressFile(archive_file_path, progress_func) 140 | with zipfile.ZipFile(archive_file) as arch: 141 | arch.extractall(output_file_path) 142 | archive_file.close() 143 | elif extension == ".7z": 144 | proc = subprocess.Popen([config['paths']['7z'], "x", archive_file_path, "-o{}".format(output_file_path)]) 145 | if proc.wait() != 0: 146 | return False 147 | elif extension in [".exe", ".msi"]: 148 | # installers need to be handled by the caller 149 | return True 150 | else: 151 | logging.error("unsupported file extension {0}".format(extension)) 152 | return False 153 | 154 | for i in range(self.__tree_depth): 155 | sub_dirs = os.listdir(output_file_path) 156 | if len(sub_dirs) != 1: 157 | raise ValueError("unexpected archive structure," 158 | " expected exactly one directory in {}".format(output_file_path)) 159 | source_dir = os.path.join(output_file_path, sub_dirs[0]) 160 | 161 | for src in os.listdir(source_dir): 162 | shutil.move(os.path.join(source_dir, src), output_file_path) 163 | 164 | shutil.rmtree(source_dir) 165 | return True 166 | -------------------------------------------------------------------------------- /unibuild/modules/cmake.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | 19 | from unibuild.builder import Builder 20 | from unibuild.utility.enum import enum 21 | from unibuild.utility.context_objects import on_exit 22 | from subprocess import Popen, PIPE 23 | from config import config 24 | import os.path 25 | import logging 26 | import shutil 27 | import re 28 | 29 | 30 | class CMake(Builder): 31 | 32 | def __init__(self): 33 | super(CMake, self).__init__() 34 | self.__arguments = [] 35 | self.__install = False 36 | 37 | @property 38 | def name(self): 39 | if self._context is None: 40 | return "cmake" 41 | else: 42 | return "cmake {0}".format(self._context.name) 43 | 44 | def applies(self, parameters): 45 | return True 46 | 47 | def fulfilled(self): 48 | return False 49 | 50 | def arguments(self, arguments): 51 | self.__arguments = arguments 52 | return self 53 | 54 | def install(self): 55 | self.__install = True 56 | return self 57 | 58 | def process(self, progress): 59 | if "build_path" not in self._context: 60 | logging.error("source path not known for {}," 61 | " are you missing a matching retrieval script?".format(self._context.name)) 62 | return False 63 | 64 | # prepare for out-of-source build 65 | build_path = os.path.join(self._context["build_path"], "build") 66 | #if os.path.exists(build_path): 67 | # shutil.rmtree(build_path) 68 | try: 69 | os.mkdir(build_path) 70 | except: 71 | pass 72 | 73 | soutpath = os.path.join(self._context["build_path"], "stdout.log") 74 | serrpath = os.path.join(self._context["build_path"], "stderr.log") 75 | 76 | try: 77 | with on_exit(lambda: progress.finish()): 78 | with open(soutpath, "w") as sout: 79 | with open(serrpath, "w") as serr: 80 | proc = Popen( 81 | [config["paths"]["cmake"], "-G", "NMake Makefiles", ".."] + self.__arguments, 82 | cwd=build_path, 83 | env=config["__environment"], 84 | stdout=sout, stderr=serr) 85 | proc.communicate() 86 | if proc.returncode != 0: 87 | raise Exception("failed to generate makefile (returncode %s), see %s and %s" % 88 | (proc.returncode, soutpath, serrpath)) 89 | 90 | proc = Popen([config['tools']['make'], "verbose=1"], 91 | shell=True, 92 | env=config["__environment"], 93 | cwd=build_path, 94 | stdout=PIPE, stderr=serr) 95 | progress.job = "Compiling" 96 | progress.maximum = 100 97 | while proc.poll() is None: 98 | while True: 99 | line = proc.stdout.readline() 100 | if line != '': 101 | match = re.search("^\\[([0-9 ][0-9 ][0-9])%\\]", line) 102 | if match is not None: 103 | progress.value = int(match.group(1)) 104 | sout.write(line) 105 | else: 106 | break 107 | 108 | if proc.returncode != 0: 109 | raise Exception("failed to build (returncode %s), see %s and %s" % 110 | (proc.returncode, soutpath, serrpath)) 111 | 112 | if self.__install: 113 | proc = Popen([config['tools']['make'], "install"], 114 | shell=True, 115 | env=config["__environment"], 116 | cwd=build_path, 117 | stdout=sout, stderr=serr) 118 | proc.communicate() 119 | if proc.returncode != 0: 120 | raise Exception("failed to install (returncode %s), see %s and %s" % 121 | (proc.returncode, soutpath, serrpath)) 122 | return False 123 | except Exception, e: 124 | logging.error(e.message) 125 | return False 126 | return True 127 | 128 | 129 | class CMakeEdit(Builder): 130 | 131 | Type = enum(VC=1, CodeBlocks=2) 132 | 133 | def __init__(self, ide_type): 134 | super(CMakeEdit, self).__init__() 135 | self.__arguments = [] 136 | self.__type = ide_type 137 | 138 | @property 139 | def name(self): 140 | if self._context is None: 141 | return "cmake edit" 142 | else: 143 | return "cmake edit {}".format(self._context.name) 144 | 145 | def applies(self, parameters): 146 | return True 147 | 148 | def fulfilled(self): 149 | return False 150 | 151 | def arguments(self, arguments): 152 | self.__arguments = arguments 153 | return self 154 | 155 | def __vc_year(self, version): 156 | if version == "12.0": 157 | return "2013" 158 | elif version == "14.0": 159 | return "2015" 160 | 161 | def __generator_name(self): 162 | if self.__type == CMakeEdit.Type.VC: 163 | return "Visual Studio {} {}"\ 164 | .format(config['vc_version'].split('.')[0], self.__vc_year(config['vc_version'])) 165 | elif self.__type == CMakeEdit.Type.CodeBlocks: 166 | return "CodeBlocks - NMake Makefiles" 167 | 168 | def prepare(self): 169 | self._context['edit_path'] = os.path.join(self._context['build_path'], "edit") 170 | 171 | def process(self, progress): 172 | if "build_path" not in self._context: 173 | logging.error("source path not known for {}," 174 | " are you missing a matching retrieval script?".format(self._context.name)) 175 | return False 176 | 177 | if os.path.exists(self._context['edit_path']): 178 | shutil.rmtree(self._context['edit_path']) 179 | os.mkdir(self._context['edit_path']) 180 | 181 | soutpath = os.path.join(self._context["build_path"], "stdout.log") 182 | serrpath = os.path.join(self._context["build_path"], "stderr.log") 183 | 184 | with open(soutpath, "w") as sout: 185 | with open(serrpath, "w") as serr: 186 | proc = Popen( 187 | [config["paths"]["cmake"], "-G", self.__generator_name(), ".."] + self.__arguments, 188 | cwd=self._context['edit_path'], 189 | env=config["__environment"], 190 | stdout=sout, stderr=serr) 191 | proc.communicate() 192 | if proc.returncode != 0: 193 | logging.error("failed to generate makefile (returncode %s), see %s and %s", 194 | proc.returncode, soutpath, serrpath) 195 | return False 196 | 197 | return True 198 | -------------------------------------------------------------------------------- /unibuild/projects/qt5.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | 19 | from unibuild import Project, Task 20 | from unibuild.modules import build, Patch, git, urldownload, sourceforge, dummy 21 | from config import config 22 | import os 23 | import itertools 24 | from glob import glob 25 | import shutil 26 | import python 27 | import errno 28 | 29 | from unibuild.projects import openssl, cygwin, icu 30 | 31 | qt_download_url = "http://download.qt.io/official_releases/qt" 32 | qt_download_ext = "tar.gz" 33 | qt_version = config['qt_version'] 34 | qt_version_minor = "1" 35 | qt_inst_path = "{}/qt5".format(config["paths"]["build"]).replace("/", os.path.sep) 36 | icu_version = config['icu_version'] 37 | 38 | 39 | def bitness(): 40 | return "64" if config['architecture'] == "x86_64" else "32" 41 | 42 | def variant(): 43 | return "msvc2013" if config['vc_version'] == "12.0" else "msvc2015" if config['vc_version'] == "14.0" else "msvc2017" 44 | 45 | qt_bin_variant = variant() 46 | 47 | platform = "win32-{0}".format(variant()) 48 | 49 | openssl_version = config['openssl_version'] 50 | grep_version = config['grep_version'] 51 | 52 | def make_sure_path_exists(path): 53 | try: 54 | os.makedirs(path) 55 | except OSError as exception: 56 | if exception.errno != errno.EEXIST: 57 | raise 58 | 59 | 60 | # if config.get('prefer_binary_dependencies', False): 61 | 62 | if False: 63 | # binary installation disabled because there is no support currently for headless installation 64 | filename = "qt-opensource-windows-x86-{variant}{arch}-{ver}.{ver_min}.exe".format( 65 | url=qt_download_url, 66 | ver=qt_version, 67 | ver_min=qt_version_minor, 68 | variant=qt_bin_variant, 69 | arch="_64" if config['architecture'] == 'x86_64' else "" 70 | ) 71 | qt5 = Project("Qt5") \ 72 | .depend(build.Run(filename, working_directory=config['paths']['download']) 73 | .depend(urldownload.URLDownload( 74 | "{url}/{ver}/{ver}.{ver_min}/{filename}" 75 | .format(url=qt_download_url, 76 | ver=qt_version, 77 | ver_min=qt_version_minor, 78 | filename=filename)))) 79 | else: 80 | skip_list = ["qtactiveqt", "qtandroidextras", "qtenginio", 81 | "qtserialport", "qtsvg", "qtwebkit", 82 | "qtwayland", "qtdoc", "qtconnectivity", "qtwebkit-examples"] 83 | 84 | nomake_list = ["tests", "examples"] 85 | 86 | configure_cmd = lambda: " ".join(["configure.bat", 87 | "-platform", platform, 88 | "-debug-and-release", "-force-debug-info", 89 | "-opensource", "-confirm-license", "-icu", 90 | "-mp", "-no-compile-examples", 91 | "-no-angle", "-opengl", "desktop", 92 | "-ssl", "-openssl-linked", 93 | "OPENSSL_LIBS=\"-lssleay32MD -llibeay32MD -lgdi32 -lUser32\"", 94 | "-prefix", qt_inst_path] \ 95 | + list(itertools.chain(*[("-skip", s) for s in skip_list])) \ 96 | + list(itertools.chain(*[("-nomake", n) for n in nomake_list]))) 97 | 98 | jom = Project("jom") \ 99 | .depend(urldownload.URLDownload("http://download.qt.io/official_releases/jom/jom.zip")) 100 | 101 | def qt5_environment(): 102 | result = config['__environment'].copy() 103 | result['Path'] = ";".join([ 104 | os.path.join(config['paths']['build'], "icu", "dist", "bin"), 105 | os.path.join(config['paths']['build'], "icu", "dist", "lib"), 106 | os.path.join(config['paths']['build'], "jom")]) + ";" + result['Path'] 107 | result['INCLUDE'] = os.path.join(config['paths']['build'], "icu", "dist", "include") + ";" + \ 108 | os.path.join(config['paths']['build'], "Win{}OpenSSL-{}".format(bitness(), openssl_version.replace(".", "_")), "include") + ";" + \ 109 | result['INCLUDE'] 110 | result['LIB'] = os.path.join(config['paths']['build'], "icu", "dist", "lib") + ";" + \ 111 | os.path.join(config['paths']['build'], "Win{}OpenSSL-{}".format(bitness(), openssl_version.replace(".", "_")), "lib", "VC") + ";" + \ 112 | result['LIB'] 113 | result['LIBPATH'] = os.path.join(config['paths']['build'], "icu", "dist", "lib") + ";" + result['LIBPATH'] 114 | return result 115 | 116 | init_repo = build.Run("perl init-repository", name="init qt repository") \ 117 | .set_fail_behaviour(Task.FailBehaviour.CONTINUE) \ 118 | .depend(git.Clone("http://code.qt.io/qt/qt5.git", qt_version)) # Internet proxy could refuse git protocol 119 | 120 | build_qt5 = build.Run(r"jom.exe -j {}".format(config['num_jobs']), 121 | environment=qt5_environment(), 122 | name="Build Qt5", 123 | working_directory=lambda: os.path.join(qt5['build_path'])) 124 | 125 | install_qt5 = build.Run(r"nmake install", 126 | environment=qt5_environment(), 127 | name="Install Qt5", 128 | working_directory=lambda: os.path.join(qt5['build_path'])) 129 | 130 | 131 | def copy_icu_libs(context): 132 | for f in glob(os.path.join(config['paths']['build'], "icu", "dist", "lib", "icu*{}.dll".format(icu_version))): 133 | shutil.copy(f, os.path.join(config["paths"]["build"], "qt5", "bin")) 134 | return True 135 | 136 | 137 | def copy_imageformats(context): 138 | make_sure_path_exists(os.path.join(config['paths']['install'], "bin", "dlls", "imageformats")) 139 | for f in glob(os.path.join(config["paths"]["build"], "qt5.git", "qtbase", "plugins", "imageformats", "*.dll")): 140 | shutil.copy(f, os.path.join(config['paths']['install'], "bin", "dlls", "imageformats")) 141 | return True 142 | 143 | 144 | def copy_platform(context): 145 | make_sure_path_exists(os.path.join(config['paths']['install'], "bin", "platforms")) 146 | for f in glob( 147 | os.path.join(config["paths"]["build"], "qt5.git", "qtbase", "plugins", "platforms", "qwindows.dll")): 148 | shutil.copy(f, os.path.join(config['paths']['install'], "bin", "platforms")) 149 | return True 150 | 151 | 152 | qt5 = Project("Qt5") \ 153 | .depend(build.Execute(copy_imageformats) 154 | .depend(build.Execute(copy_platform) 155 | .depend(build.Execute(copy_icu_libs) 156 | .depend(install_qt5 157 | .depend(build_qt5 158 | .depend("jom") 159 | .depend(build.Run(configure_cmd, 160 | name="configure qt", 161 | environment=qt5_environment()) 162 | .depend(init_repo) 163 | 164 | .depend("icu") 165 | .depend("openssl") 166 | ) 167 | ) 168 | ) 169 | ) 170 | ) 171 | ) 172 | 173 | 174 | -------------------------------------------------------------------------------- /unimake.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | 19 | import eggs 20 | from unibuild.manager import TaskManager 21 | from unibuild.progress import Progress 22 | from unibuild.project import Project 23 | from unibuild import Task 24 | from unibuild.utility import CIDict 25 | from config import config, vs_editions, get_from_hklm 26 | from subprocess import Popen, PIPE 27 | import imp 28 | import sys 29 | import traceback 30 | import logging 31 | import networkx as nx 32 | import tempfile 33 | import os.path 34 | import argparse 35 | import re 36 | 37 | exitcode = 0 38 | 39 | def progress_callback(job, percentage): 40 | if not percentage and not job: 41 | sys.stdout.write("\n") 42 | else: 43 | pb_length = 50 44 | filled = int((pb_length * percentage) / 100) # cast to int may be necessary in python 3 45 | #sys.stdout.write("\r%d%%" % percentage) 46 | sys.stdout.write("\r%s [%s%s] %d%%" % (job, "=" * filled, " " * (pb_length - filled), percentage)) 47 | 48 | sys.stdout.flush() 49 | 50 | 51 | def draw_graph(graph, filename): 52 | if config['paths']['graphviz']: 53 | # neither pydot nor pygraphviz reliably find graphviz on windows. gotta do everything myself... 54 | from subprocess import call 55 | graph_file_name = os.path.join(os.getcwd(), "graph.dot") 56 | nx.write_dot(graph, graph_file_name) 57 | 58 | call([config['paths']['graphviz'], 59 | "-Tpng", "-Edir=back", "-Gsplines=ortho", "-Grankdir=BT", "-Gconcentrate=true", "-Nshape=box", "-Gdpi=192", 60 | graph_file_name, 61 | "-o", "{}.png".format(filename)]) 62 | else: 63 | print("graphviz path not set") 64 | 65 | 66 | def extract_independent(graph): 67 | """ 68 | :param graph: 69 | :type graph: nx.DiGraph 70 | :return: 71 | """ 72 | independent = [] 73 | for node in graph.nodes_iter(): 74 | if graph.out_degree(node) == 0: 75 | independent.append(node) 76 | return independent 77 | 78 | 79 | def vc_year(vc_version): 80 | return "2017" if vc_version == "15.0" else "" 81 | 82 | 83 | # No entries for vs 2017 in the stadard registry, check environment then look in the default installation dir 84 | def get_visual_studio_2017_or_more(vc_version): 85 | try: 86 | if os.environ["VisualStudioVersion"] == vc_version: 87 | p = os.path.join(os.environ["VSINSTALLDIR"], "VC", "Auxiliary", "Build") 88 | f = os.path.join(p, "vcvarsall.bat") 89 | res = os.path.isfile(f) 90 | if res is not None: 91 | return os.path.realpath(p) 92 | else: 93 | res = None 94 | except: 95 | res = None 96 | 97 | try: 98 | p = os.path.join(config['vc_CustomInstallPath'], "VC", "Auxiliary", "Build") 99 | f = os.path.join(p, "vcvarsall.bat") 100 | res = os.path.isfile(f) 101 | if res is not None: 102 | return os.path.realpath(p) 103 | else: 104 | res = None 105 | except: 106 | res = None 107 | 108 | for edition in vs_editions: 109 | s = os.environ["ProgramFiles(x86)"] 110 | p = os.path.join(s, "Microsoft Visual Studio", vc_year(vc_version), edition, "VC", "Auxiliary", "Build") 111 | f = os.path.join(p, "vcvarsall.bat") 112 | if os.path.isfile(f): 113 | config['paths']['visual_studio_basedir'] = os.path.join(s, "Microsoft Visual Studio", vc_year(vc_version), edition) 114 | return os.path.realpath(p) 115 | 116 | 117 | def get_visual_studio_2015_or_less(vc_version): 118 | res = "" 119 | try: 120 | s = os.environ["ProgramFiles(x86)"] 121 | p = os.path.join(s, "Microsoft Visual Studio {}".format(vc_version), "VC") 122 | f = os.path.join(p, "vcvarsall.bat") 123 | if os.path.isfile(f): 124 | config['paths']['visual_studio_basedir'] = os.path.join(s, "Microsoft Visual Studio {}".format(vc_version)) 125 | return os.path.realpath(p) 126 | else: 127 | res = None 128 | except: 129 | res = None 130 | 131 | if res == None: 132 | try: 133 | s = os.environ["ProgramFiles(x86)"] 134 | p = os.path.join(s, "Microsoft Visual Studio", "Shared", vc_version, "VC") 135 | f = os.path.join(p, "vcvarsall.bat") 136 | 137 | if os.path.isfile(f): 138 | config['paths']['visual_studio_basedir'] = os.path.join(s, "Microsoft Visual Studio", "Shared", vc_version) 139 | return os.path.realpath(p) 140 | else: 141 | res = None 142 | except: 143 | res = None 144 | 145 | # We should try the custom VC install path as well 146 | if res == None: 147 | try: 148 | p = os.path.join(config['vc_CustomInstallPath'], "VC") 149 | f = os.path.join(p, "vcvarsall.bat") 150 | if os.path.isfile(f): 151 | config['paths']['visual_studio_basedir'] = os.path.join(config['vc_CustomInstallPath']) 152 | return os.path.realpath(p) 153 | else: 154 | res = None 155 | except: 156 | res = None 157 | 158 | 159 | 160 | def visual_studio(vc_version): 161 | config["paths"]["visual_studio"] = get_visual_studio_2015_or_less(vc_version) if vc_version < "15.0" else get_visual_studio_2017_or_more(vc_version) 162 | if not config["paths"]["visual_studio"]: 163 | logging.error("Unable to find vcvarsall.bat, please make sure you have 'Common C++ tools' Installed") 164 | return False 165 | 166 | 167 | def visual_studio_environment(): 168 | # when using visual studio we need to set up the environment correctly 169 | arch = "amd64" if config["architecture"] == 'x86_64' else "x86" 170 | if config['paths']['visual_studio']: 171 | proc = Popen([os.path.join(config['paths']['visual_studio'], "vcvarsall.bat"), arch, "&&", "SET"], 172 | stdout=PIPE, stderr=PIPE) 173 | stdout, stderr = proc.communicate() 174 | 175 | if "Error in script usage. The correct usage is" in stderr: 176 | logging.error("failed to set up environment (returncode %s): %s", proc.returncode, stderr) 177 | return False 178 | 179 | if "Error in script usage. The correct usage is" in stdout: 180 | logging.error("failed to set up environment (returncode %s): %s", proc.returncode, stderr) 181 | return False 182 | 183 | if proc.returncode != 0: 184 | logging.error("failed to set up environment (returncode %s): %s", proc.returncode, stderr) 185 | return False 186 | else: 187 | sys.exit(1) 188 | 189 | 190 | vcenv = CIDict() 191 | 192 | for line in stdout.splitlines(): 193 | if "=" in line: 194 | key, value = line.split("=", 1) 195 | vcenv[key] = value 196 | return vcenv 197 | 198 | 199 | def init_config(args): 200 | for d in config['paths'].keys(): 201 | if isinstance(config['paths'][d], str): 202 | config['paths'][d] = config['paths'][d].format(base_dir=os.path.abspath(args.destination), 203 | build_dir=args.builddir, 204 | progress_dir=args.progressdir, 205 | install_dir=args.installdir) 206 | 207 | 208 | if args.set: 209 | for setting in args.set: 210 | key, value = setting.split('=', 2) 211 | path = key.split('.') 212 | cur = config 213 | for ele in path[:-1]: 214 | cur = cur.setdefault(ele, {}) 215 | cur[path[-1]] = value 216 | 217 | if config['architecture'] not in ['x86_64', 'x86']: 218 | raise ValueError("only architectures supported are x86 and x86_64") 219 | 220 | visual_studio(config["vc_version"]) # forced set after args are evaluated 221 | config['__Default_environment'] = os.environ 222 | config['__environment'] = visual_studio_environment() 223 | config['__build_base_path'] = os.path.abspath(args.destination) 224 | 225 | if 'PYTHON' not in config['__environment']: 226 | config['__environment']['PYTHON'] = sys.executable 227 | 228 | def recursive_remove(graph, node): 229 | if not isinstance(graph.node[node]["task"], Project): 230 | for ancestor in graph.predecessors(node): 231 | recursive_remove(graph, ancestor) 232 | graph.remove_node(node) 233 | 234 | 235 | def main(): 236 | time_format = "%(asctime)-15s %(message)s" 237 | logging.basicConfig(format=time_format, level=logging.DEBUG) 238 | 239 | parser = argparse.ArgumentParser() 240 | parser.add_argument('-f', '--file', default='makefile.uni.py', help='sets the build script') 241 | parser.add_argument('-d', '--destination', default='.', help='output directory (base for download and build)') 242 | parser.add_argument('-s', '--set', action='append', help='set configuration parameters') 243 | parser.add_argument('-g', '--graph', action='store_true', help='update dependency graph') 244 | parser.add_argument('-b', '--builddir', default='build', help='update build directory') 245 | parser.add_argument('-p', '--progressdir', default='progress', help='update progress directory') 246 | parser.add_argument('-i', '--installdir', default='install', help='update progress directory') 247 | parser.add_argument('target', nargs='*', help='make target') 248 | args = parser.parse_args() 249 | 250 | init_config(args) 251 | 252 | for d in ["download", "build", "progress","install"]: 253 | if not os.path.exists(config["paths"][d]): 254 | os.makedirs(config["paths"][d]) 255 | 256 | logging.debug("building dependency graph") 257 | manager = TaskManager() 258 | imp.load_source(args.builddir, args.file) 259 | build_graph = manager.create_graph({}) 260 | assert isinstance(build_graph, nx.DiGraph) 261 | 262 | if args.graph: 263 | draw_graph(build_graph, "graph") 264 | 265 | cycles = list(nx.simple_cycles(build_graph)) 266 | if cycles: 267 | logging.error("There are cycles in the build graph") 268 | for cycle in cycles: 269 | logging.info(", ".join(cycle)) 270 | return 1 271 | 272 | if args.target: 273 | for target in args.target: 274 | manager.enable(build_graph, target) 275 | else: 276 | manager.enable_all(build_graph) 277 | 278 | logging.debug("processing tasks") 279 | independent = extract_independent(build_graph) 280 | 281 | while independent: 282 | for node in independent: 283 | task = build_graph.node[node]['task'] 284 | try: 285 | task.prepare() 286 | if build_graph.node[node]['enable'] and not task.already_processed(): 287 | progress = Progress() 288 | progress.set_change_callback(progress_callback) 289 | if isinstance(task, Project): 290 | logging.debug("finished project \"{}\"".format(node)) 291 | else: 292 | logging.debug("run task \"{}\"".format(node)) 293 | if task.process(progress): 294 | task.mark_success() 295 | else: 296 | if task.fail_behaviour == Task.FailBehaviour.FAIL: 297 | logging.critical("task %s failed", node) 298 | exitcode = 1 299 | return 1 300 | elif task.fail_behaviour == Task.FailBehaviour.SKIP_PROJECT: 301 | recursive_remove(build_graph, node) 302 | break 303 | elif task.fail_behaviour == Task.FailBehaviour.CONTINUE: 304 | # nothing to do 305 | pass 306 | sys.stdout.write("\n") 307 | except Exception, e: 308 | logging.error("Task {} failed: {}".format(task.name, e)) 309 | raise 310 | 311 | build_graph.remove_node(node) 312 | 313 | independent = extract_independent(build_graph) 314 | 315 | 316 | if __name__ == "__main__": 317 | try: 318 | exitcode = main() 319 | if not exitcode == 0: 320 | sys.exit(exitcode) 321 | except Exception, e: 322 | traceback.print_exc(file=sys.stdout) 323 | sys.exit(1) 324 | 325 | 326 | -------------------------------------------------------------------------------- /makefile.uni.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | 19 | from unibuild import Project 20 | from unibuild.modules import github, cmake, Patch, git, hg, msbuild, build, dummy 21 | from unibuild.utility import lazy, FormatDict 22 | from config import config 23 | from functools import partial 24 | from string import Formatter 25 | import os, sys 26 | 27 | 28 | """ 29 | Settings 30 | """ 31 | 32 | loot_version = "0.10.3" 33 | commit_id = "g0fcf788" 34 | 35 | """ 36 | Projects 37 | """ 38 | 39 | 40 | from unibuild.projects import sevenzip, qt5, boost, zlib, python, sip, pyqt5, ncc 41 | from unibuild.projects import asmjit, udis86, googletest, spdlog, fmtlib, lz4, WixToolkit 42 | 43 | # TODO modorganizer-lootcli needs an overhaul as the api has changed alot 44 | def bitness(): 45 | return "x64" if config['architecture'] == "x86_64" else "Win32" 46 | 47 | Project("LootApi") \ 48 | .depend(Patch.Copy("loot_api.dll".format(loot_version, commit_id), os.path.join(config["paths"]["install"], "bin", "loot")) 49 | .depend(github.Release("loot", "loot", loot_version, "loot-api_{}-0-{}_dev_{}".format(loot_version, commit_id, bitness()),"7z",tree_depth=1) 50 | .set_destination("lootapi")) 51 | ) 52 | 53 | 54 | tl_repo = git.SuperRepository("modorganizer_super") 55 | 56 | def gen_userfile_content(project): 57 | with open("CMakeLists.txt.user.template", 'r') as f: 58 | res = Formatter().vformat(f.read(), [], FormatDict({ 59 | 'build_dir' : project['edit_path'], 60 | 'environment_id': config['qt_environment_id'], 61 | 'profile_name' : config['qt_profile_name'], 62 | 'profile_id' : config['qt_profile_id'] 63 | })) 64 | return res 65 | 66 | 67 | cmake_parameters = [ 68 | "-DCMAKE_BUILD_TYPE={}".format(config["build_type"]), 69 | "-DDEPENDENCIES_DIR={}".format(config["paths"]["build"]), 70 | # boost git version "-DBOOST_ROOT={}/build/boostgit", 71 | "-DBOOST_ROOT={}/boost_{}".format(config["paths"]["build"], config["boost_version"].replace(".", "_")), 72 | ] 73 | 74 | 75 | if config.get('optimize', False): 76 | cmake_parameters.append("-DOPTIMIZE_LINK_FLAGS=\"/LTCG /INCREMENTAL:NO /OPT:REF /OPT:ICF\"") 77 | 78 | 79 | usvfs = Project("usvfs") 80 | 81 | usvfs.depend(cmake.CMake().arguments(cmake_parameters + 82 | ["-DCMAKE_INSTALL_PREFIX:PATH={}".format(config["paths"]["install"])] + 83 | ["-DPROJ_ARCH={}".format("x86" if config['architecture'] == 'x86' else "x64")]) 84 | .install() 85 | # TODO Not sure why this is required, will look into it at a later stage once we get the rest to build 86 | .depend(github.Source(config['Main_Author'], "usvfs", "master") 87 | .set_destination("usvfs")) 88 | .depend("AsmJit") 89 | .depend("Udis86") 90 | .depend("GTest") 91 | .depend("fmtlib") 92 | .depend("spdlog") 93 | .depend("boost") 94 | ) 95 | 96 | 97 | if config['architecture'] == 'x86_64': 98 | usvfs_32 = Project("usvfs_32") 99 | usvfs_32.depend(build.Run_With_Output(r'"{0}" unimake.py -d "{1}" --set architecture="x86" -b "build_32" -p "progress_32" -i "install_32" usvfs'.format(sys.executable,config['__build_base_path']), 100 | name="Building usvfs 32bit Dll",environment=config['__Default_environment'],working_directory=os.path.join(os.getcwd()))) 101 | else: 102 | usvfs_32 = Project("usvfs_32") 103 | usvfs_32.depend(dummy.Success("usvfs_32")) 104 | 105 | for author, git_path, path, branch, dependencies, Build in [ 106 | (config['Main_Author'], "modorganizer-game_features", "game_features", "master", [],False), 107 | (config['Main_Author'], "modorganizer-archive", "archive", "master", ["7zip", "Qt5"],True), 108 | (config['Main_Author'], "modorganizer-uibase", "uibase", "QT5.7", ["Qt5", "boost"],True), 109 | (config['Main_Author'], "modorganizer-lootcli", "lootcli", "master", ["LootApi", "boost"],True), 110 | (config['Main_Author'], "modorganizer-esptk", "esptk", "master", ["boost"],True), 111 | (config['Main_Author'], "modorganizer-bsatk", "bsatk", "master", ["zlib","boost"],True), 112 | (config['Main_Author'], "modorganizer-nxmhandler", "nxmhandler", "master", ["Qt5"],True), 113 | (config['Main_Author'], "modorganizer-helper", "helper", "master", ["Qt5"],True), 114 | (config['Main_Author'], "modorganizer-game_gamebryo", "game_gamebryo", "new_vfs_library", ["Qt5", "modorganizer-uibase", 115 | "modorganizer-game_features", "lz4"],True), 116 | (config['Main_Author'], "modorganizer-game_oblivion", "game_oblivion", "master", ["Qt5", "modorganizer-uibase", 117 | "modorganizer-game_gamebryo", 118 | "modorganizer-game_features"],True), 119 | (config['Main_Author'], "modorganizer-game_fallout3", "game_fallout3", "master", ["Qt5", "modorganizer-uibase", 120 | "modorganizer-game_gamebryo", 121 | "modorganizer-game_features"],True), 122 | (config['Main_Author'], "modorganizer-game_fallout4", "game_fallout4", "master", ["Qt5", "modorganizer-uibase", 123 | "modorganizer-game_gamebryo", 124 | "modorganizer-game_features"],True), 125 | (config['Main_Author'], "modorganizer-game_falloutnv", "game_falloutnv", "master", ["Qt5", "modorganizer-uibase", 126 | "modorganizer-game_gamebryo", 127 | "modorganizer-game_features"],True), 128 | (config['Main_Author'], "modorganizer-game_skyrim", "game_skyrim", "master", ["Qt5", "modorganizer-uibase", 129 | "modorganizer-game_gamebryo", 130 | "modorganizer-game_features"],True), 131 | ("LePresidente", "modorganizer-game_skyrimSE", "game_skyrimse", "dev", ["Qt5", "modorganizer-uibase", 132 | "modorganizer-game_gamebryo", 133 | "modorganizer-game_features"],True), 134 | (config['Main_Author'], "modorganizer-tool_inieditor", "tool_inieditor", "master", ["Qt5", "modorganizer-uibase"],True), 135 | (config['Main_Author'], "modorganizer-tool_inibakery", "tool_inibakery", "master", ["modorganizer-uibase"],True), 136 | (config['Main_Author'], "modorganizer-tool_configurator", "tool_configurator", "QT5.7", ["PyQt5"],True), 137 | (config['Main_Author'], "modorganizer-preview_base", "preview_base", "master", ["Qt5", "modorganizer-uibase"],True), 138 | (config['Main_Author'], "modorganizer-diagnose_basic", "diagnose_basic", "master", ["Qt5", "modorganizer-uibase"],True), 139 | (config['Main_Author'], "modorganizer-check_fnis", "check_fnis", "master", ["Qt5", "modorganizer-uibase"],True), 140 | (config['Main_Author'], "modorganizer-installer_bain", "installer_bain", "QT5.7", ["Qt5", "modorganizer-uibase"],True), 141 | (config['Main_Author'], "modorganizer-installer_manual", "installer_manual", "QT5.7", ["Qt5", "modorganizer-uibase"],True), 142 | (config['Main_Author'], "modorganizer-installer_bundle", "installer_bundle", "master", ["Qt5", "modorganizer-uibase"],True), 143 | (config['Main_Author'], "modorganizer-installer_quick", "installer_quick", "master", ["Qt5", "modorganizer-uibase"],True), 144 | (config['Main_Author'], "modorganizer-installer_fomod", "installer_fomod", "master", ["Qt5", "modorganizer-uibase"],True), 145 | (config['Main_Author'], "modorganizer-installer_ncc", "installer_ncc", "master", ["Qt5", "modorganizer-uibase", "NCC"],True), 146 | (config['Main_Author'], "modorganizer-bsa_extractor", "bsa_extractor", "master", ["Qt5", "modorganizer-uibase"],True), 147 | (config['Main_Author'], "modorganizer-plugin_python", "plugin_python", "master", ["Qt5", "boost", "Python", "modorganizer-uibase", 148 | "sip"],True), 149 | (config['Main_Author'], "githubpp", "githubpp", "master", ["Qt5"],True), 150 | (config['Main_Author'], "modorganizer", "modorganizer", "QT5.7", ["Qt5", "boost", "usvfs_32", 151 | "modorganizer-uibase", "modorganizer-archive", 152 | "modorganizer-bsatk", "modorganizer-esptk", 153 | "modorganizer-game_features", 154 | "usvfs","githubpp", "NCC"], True), 155 | ]: 156 | build_step = cmake.CMake().arguments(cmake_parameters + 157 | ["-DCMAKE_INSTALL_PREFIX:PATH={}".format(config["paths"]["install"])])\ 158 | .install() 159 | 160 | for dep in dependencies: 161 | build_step.depend(dep) 162 | 163 | project = Project(git_path) 164 | 165 | if Build: 166 | project.depend(build_step.depend(github.Source(author, git_path, branch, super_repository=tl_repo) 167 | .set_destination(path))) 168 | else: 169 | project.depend(github.Source(author, git_path, branch, super_repository=tl_repo) 170 | .set_destination(path)) 171 | 172 | 173 | 174 | def python_zip_collect(context): 175 | import libpatterns 176 | import glob 177 | from zipfile import ZipFile 178 | 179 | ip = os.path.join(config["paths"]["install"], "bin") 180 | bp = python.python['build_path'] 181 | 182 | with ZipFile(os.path.join(ip, "python27.zip"), "w") as pyzip: 183 | for pattern in libpatterns.patterns: 184 | for f in glob.iglob(os.path.join(bp, pattern)): 185 | pyzip.write(f, f[len(bp):]) 186 | 187 | return True 188 | 189 | 190 | Project("python_zip") \ 191 | .depend(build.Execute(python_zip_collect) 192 | .depend("Python") 193 | ) 194 | 195 | if config['Installer']: 196 | #build_installer = cmake.CMake().arguments(cmake_parameters +["-DCMAKE_INSTALL_PREFIX:PATH={}/installer".format(config["__build_base_path"])]).install() 197 | wixinstaller = Project("WixInstaller") 198 | 199 | wixinstaller.depend(github.Source(config['Main_Author'],"modorganizer-WixInstaller", "VSDev", super_repository=tl_repo) 200 | .set_destination("WixInstaller"))\ 201 | .depend("modorganizer").depend("usvfs").depend("usvfs_32") 202 | 203 | 204 | -------------------------------------------------------------------------------- /unibuild/modules/build.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 Sebastian Herbord. All rights reserved. 2 | # 3 | # This file is part of Mod Organizer. 4 | # 5 | # Mod Organizer is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # Mod Organizer is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with Mod Organizer. If not, see . 17 | 18 | 19 | from unibuild import Task 20 | from unibuild.builder import Builder 21 | from unibuild.utility.lazy import Lazy 22 | from subprocess import Popen 23 | from config import config 24 | import os.path 25 | import logging 26 | 27 | STATIC_LIB = 1 28 | SHARED_LIB = 2 29 | EXECUTABLE = 3 30 | 31 | 32 | class CPP(Builder): 33 | 34 | def __init__(self, cflags=None): 35 | super(CPP, self).__init__() 36 | self.__type = EXECUTABLE 37 | self.__targets = [] 38 | self.__cflags = cflags or ["-nologo", "-O2", "-MD"] 39 | 40 | @property 41 | def name(self): 42 | if self._context is None: 43 | return "custom build" 44 | else: 45 | return "custom build {0}".format(self._context.name) 46 | 47 | def fulfilled(self): 48 | return False 49 | 50 | def type(self, build_type): 51 | self.__type = build_type 52 | return self 53 | 54 | def __gen_build_cmd(self, target, files): 55 | if self.__type == STATIC_LIB: 56 | return "link.exe /lib /nologo /out:{0}.lib {1}".format( 57 | target, " ".join([self.__to_obj(f) for f in files])) 58 | else: 59 | raise NotImplementedError("type {} not yet implemented", self.__type) 60 | 61 | def sources(self, target, files, top_level=True): 62 | self.__targets.append((target, files, self.__gen_build_cmd(target, files), top_level)) 63 | return self 64 | 65 | def custom(self, target, dependencies=None, cmd=None, top_level=False): 66 | self.__targets.append((target, dependencies, cmd, top_level)) 67 | return self 68 | 69 | @staticmethod 70 | def __to_obj(filename): 71 | return "{}.obj".format(os.path.splitext(os.path.basename(filename))[0]) 72 | 73 | def gen_makefile(self, path): 74 | with open(os.path.join(path, "unimakefile"), "w") as mf: 75 | mf.write("CFLAGS={}\n\n".format(" ".join(self.__cflags))) 76 | for target in self.__targets: 77 | files = target[1] or [] 78 | for f in files: 79 | mf.write("{}:\n".format(self.__to_obj(f))) 80 | mf.write("\t{cl} -c $(CFLAGS) -Fo {file}\n\n".format(cl="cl", file=f)) 81 | 82 | mf.write("{0}: {1}\n\t{2}\n\n".format(target[0], 83 | " ".join([self.__to_obj(f) for f in files]), 84 | target[2] or "")) 85 | 86 | mf.write("all: {}\n".format(" ".join([target[0] 87 | for target in self.__targets 88 | if target[3]]))) 89 | 90 | def process(self, progress): 91 | path = self._context["build_path"] 92 | 93 | self.gen_makefile(path) 94 | 95 | soutpath = os.path.join(self._context["build_path"], "stdout.log") 96 | serrpath = os.path.join(self._context["build_path"], "stderr.log") 97 | with open(soutpath, "a") as sout: 98 | with open(serrpath, "a") as serr: 99 | command = "{} /f unimakefile all".format(config["tools"]["make"]) 100 | sout.write("running {} in {}\n".format(command, self._context['build_path'])) 101 | proc = Popen(command, 102 | env=config["__environment"], 103 | cwd=self._context["build_path"], 104 | shell=True, 105 | stdout=sout, stderr=serr) 106 | proc.communicate() 107 | if proc.returncode != 0: 108 | logging.error("failed to build custom makefile (returncode %s), see %s and %s", 109 | proc.returncode, soutpath, serrpath) 110 | return False 111 | return True 112 | 113 | 114 | class Install(Builder): 115 | def __init__(self, make_tool=None): 116 | super(Install, self).__init__() 117 | self.__make_tool = Lazy(make_tool or config['tools']['make']) 118 | 119 | @property 120 | def name(self): 121 | if self._context is None: 122 | return "make install" 123 | else: 124 | return "make install {0}".format(self._context.name) 125 | 126 | def process(self, progress): 127 | if "build_path" not in self._context: 128 | logging.error("source path not known for {}," 129 | " are you missing a matching retrieval script?".format(self.name())) 130 | soutpath = os.path.join(self._context["build_path"], "stdout.log") 131 | serrpath = os.path.join(self._context["build_path"], "stderr.log") 132 | 133 | with open(soutpath, "a") as sout: 134 | with open(serrpath, "a") as serr: 135 | proc = Popen([self.__make_tool(), "install"], 136 | shell=True, 137 | env=config["__environment"], 138 | cwd=self._context["build_path"], 139 | stdout=sout, stderr=serr) 140 | proc.communicate() 141 | if proc.returncode != 0: 142 | logging.error("failed to install (returncode %s), see %s and %s", 143 | proc.returncode, soutpath, serrpath) 144 | return False 145 | return True 146 | 147 | 148 | class Make(Builder): 149 | def __init__(self, make_tool=None, environment=None, working_directory=None): 150 | super(Make, self).__init__() 151 | self.__install = False 152 | self.__make_tool = Lazy(make_tool or config['tools']['make']) 153 | self.__environment = Lazy(environment) 154 | self.__working_directory = Lazy(working_directory) 155 | 156 | @property 157 | def name(self): 158 | if self._context is None: 159 | return "make" 160 | else: 161 | return "make {0}".format(self._context.name) 162 | 163 | def install(self): 164 | self.__install = True 165 | return self 166 | 167 | def process(self, progress): 168 | if "build_path" not in self._context: 169 | logging.error("source path not known for {}," 170 | " are you missing a matching retrieval script?".format(self.name())) 171 | soutpath = os.path.join(self._context["build_path"], "stdout.log") 172 | serrpath = os.path.join(self._context["build_path"], "stderr.log") 173 | 174 | with open(soutpath, "a") as sout: 175 | with open(serrpath, "a") as serr: 176 | environment = dict(self.__environment() 177 | if self.__environment() is not None 178 | else config["__environment"]) 179 | cwd = str(self.__working_directory() 180 | if self.__working_directory() is not None 181 | else self._context["build_path"]) 182 | 183 | proc = Popen(self.__make_tool().split(" "), 184 | env=environment, 185 | cwd=cwd, 186 | shell=True, 187 | stdout=sout, stderr=serr) 188 | proc.communicate() 189 | if proc.returncode != 0: 190 | logging.error("failed to run make (returncode %s), see %s and %s", 191 | proc.returncode, soutpath, serrpath) 192 | return False 193 | 194 | if self.__install: 195 | proc = Popen([config['tools']['make'], "install"], 196 | shell=True, 197 | env=environment, 198 | cwd=cwd, 199 | stdout=sout, stderr=serr) 200 | proc.communicate() 201 | if proc.returncode != 0: 202 | logging.error("failed to install (returncode %s), see %s and %s", 203 | proc.returncode, soutpath, serrpath) 204 | return False 205 | 206 | return True 207 | 208 | 209 | class Execute(Builder): 210 | def __init__(self, function, name=None): 211 | super(Execute, self).__init__() 212 | self.__function = function 213 | self.__name = name 214 | 215 | @property 216 | def name(self): 217 | if self._context is None: 218 | return "execute {}".format(self.__name or self.__function.func_name) 219 | else: 220 | return "execute {}_{}".format(self._context.name, self.__name or self.__function.func_name) 221 | 222 | def process(self, progress): 223 | return self.__function(context=self._context) 224 | 225 | 226 | class Run(Builder): 227 | def __init__(self, command, fail_behaviour=Task.FailBehaviour.FAIL, environment=None, working_directory=None, 228 | name=None): 229 | super(Run, self).__init__() 230 | self.__command = Lazy(command) 231 | self.__name = name 232 | self.__fail_behaviour = fail_behaviour 233 | self.__environment = Lazy(environment) 234 | self.__working_directory = Lazy(working_directory) 235 | 236 | @property 237 | def name(self): 238 | if self.__name: 239 | return "run {}".format(self.__name) 240 | else: 241 | return "run {}".format(self.__command.peek().split()[0]).replace("\\", "/") 242 | 243 | def process(self, progress): 244 | if "build_path" not in self._context: 245 | logging.error("source path not known for {}," 246 | " are you missing a matching retrieval script?".format(self.name)) 247 | 248 | soutpath = os.path.join(self._context["build_path"], "stdout.log") 249 | serrpath = os.path.join(self._context["build_path"], "stderr.log") 250 | with open(soutpath, "w") as sout: 251 | with open(serrpath, "w") as serr: 252 | environment = dict(self.__environment() 253 | if self.__environment() is not None 254 | else config["__environment"]) 255 | cwd = str(self.__working_directory() 256 | if self.__working_directory() is not None 257 | else self._context["build_path"]) 258 | 259 | sout.write("running {} in {}".format(self.__command(), cwd)) 260 | proc = Popen(self.__command(), 261 | env=environment, 262 | cwd=cwd, 263 | shell=True, 264 | stdout=sout, stderr=serr) 265 | proc.communicate() 266 | if proc.returncode != 0: 267 | logging.error("failed to run %s (returncode %s), see %s and %s", 268 | self.__command(), proc.returncode, soutpath, serrpath) 269 | return False 270 | 271 | return True 272 | 273 | class Run_With_Output(Builder): 274 | def __init__(self, command, fail_behaviour=Task.FailBehaviour.FAIL, environment=None, working_directory=None, 275 | name=None): 276 | super(Run_With_Output, self).__init__() 277 | self.__command = Lazy(command) 278 | self.__name = name 279 | self.__fail_behaviour = fail_behaviour 280 | self.__environment = Lazy(environment) 281 | self.__working_directory = Lazy(working_directory) 282 | 283 | @property 284 | def name(self): 285 | if self.__name: 286 | return "run {}".format(self.__name) 287 | else: 288 | return "run {}".format(self.__command.peek().split()[0]).replace("\\", "/") 289 | 290 | def process(self, progress): 291 | 292 | environment = dict(self.__environment() 293 | if self.__environment() is not None 294 | else config["__environment"]) 295 | cwd = str(self.__working_directory() 296 | if self.__working_directory() is not None 297 | else self._context["build_path"]) 298 | 299 | 300 | proc = Popen(self.__command(), 301 | env=environment, 302 | cwd=cwd, 303 | shell=True) 304 | proc.communicate() 305 | if proc.returncode != 0: 306 | if isinstance(proc.returncode , (str, unicode)): 307 | logging.error("failed to run %s (returncode %s), see %s and %s", 308 | self.__command(), proc.returncode) 309 | return False 310 | else: 311 | logging.error("failed to run {} (returncode {})".format(self.__command(), proc.returncode)) 312 | return False 313 | 314 | return True 315 | --------------------------------------------------------------------------------