#!/usr/bin/env python3 from __future__ import print_function import sys import os import platform import errno import re import shutil import hashlib from distutils.core import setup from distutils.extension import Extension from awlsim.common.version import VERSION_STRING try: import py2exe except ImportError as e: py2exe = None try: if py2exe and "py2exe" in sys.argv: raise ImportError from cx_Freeze import setup, Executable cx_Freeze = True except ImportError as e: cx_Freeze = False isWindows = os.name.lower() in {"nt", "ce"} def getEnvInt(name, default = 0): try: return int(os.getenv(name, "%d" % default)) except ValueError: return default def getEnvBool(name, default = False): return bool(getEnvInt(name, 1 if default else 0)) fullBuild = getEnvBool("AWLSIM_FULL_BUILD") buildCython = getEnvBool("AWLSIM_CYTHON", True) cythonParallelBuild = bool(getEnvInt("AWLSIM_CYTHON_PARALLEL", 1) == 1 or\ getEnvInt("AWLSIM_CYTHON_PARALLEL", 1) == sys.version_info[0]) def makedirs(path, mode=0o755): try: os.makedirs(path, mode) except OSError as e: if e.errno == errno.EEXIST: return raise e def hashFile(path): if sys.version_info[0] < 3: ExpectedException = IOError else: ExpectedException = FileNotFoundError try: return hashlib.sha1(open(path, "rb").read()).hexdigest() except ExpectedException as e: return None def __fileopIfChanged(fromFile, toFile, fileop): toFileHash = hashFile(toFile) if toFileHash is not None: fromFileHash = hashFile(fromFile) if toFileHash == fromFileHash: return False makedirs(os.path.dirname(toFile)) fileop(fromFile, toFile) return True def copyIfChanged(fromFile, toFile): return __fileopIfChanged(fromFile, toFile, shutil.copy2) def moveIfChanged(fromFile, toFile): return __fileopIfChanged(fromFile, toFile, os.rename) def makeDummyFile(path): if os.path.isfile(path): return print("creating dummy file '%s'" % path) makedirs(os.path.dirname(path)) fd = open(path, "w") fd.write("\n") fd.close() def pyCythonPatch(fromFile, toFile, basicOnly=False): print("cython-patch: patching file '%s' to '%s'" %\ (fromFile, toFile)) tmpFile = toFile + ".TMP" makedirs(os.path.dirname(tmpFile)) infd = open(fromFile, "r") outfd = open(tmpFile, "w") for line in infd.readlines(): stripLine = line.strip() if stripLine.endswith("# Is Cython installed?") buildCython = False if buildCython: if sys.version_info[0] < 3: # Cython2 build libraries need method pickling # for parallel build. def unpickle_method(fname, obj, cls): # Ignore MRO. We don't seem to inherit methods. return cls.__dict__[fname].__get__(obj, cls) def pickle_method(m): return unpickle_method, (m.im_func.__name__, m.im_self, m.im_class) import copy_reg, types copy_reg.pickle(types.MethodType, pickle_method, unpickle_method) def cyBuildWrapper(arg): # This function does the same thing as the for-loop-body # inside of Cython's build_ext.build_extensions() method. # It is called via multiprocessing to build extensions # in parallel. # Note that this might break, if Cython's build_extensions() # is changed and stuff is added to its for loop. Meh. self, ext = arg ext.sources = self.cython_sources(ext.sources, ext) self.build_extension(ext) # Override Cython's build_ext class. class MyCythonBuildExt(Cython_build_ext): def build_extension(self, ext): assert(not ext.name.endswith("__init__")) Cython_build_ext.build_extension(self, ext) def build_extensions(self): # First patch the files, the run the build patchCythonModules(self.build_lib) if cythonParallelBuild: # Run the parallel build, yay. self.check_extensions_list(self.extensions) from multiprocessing.pool import Pool Pool().map(cyBuildWrapper, ((self, ext) for ext in self.extensions)) else: # Run the normal non-parallel build. Cython_build_ext.build_extensions(self) cmdclass["build_ext"] = MyCythonBuildExt registerCythonModules() # Workaround for mbcs codec bug in distutils # http://bugs.python.org/issue10945 import codecs try: codecs.lookup("mbcs") except LookupError: codecs.register(lambda name: codecs.lookup("ascii") if name == "mbcs" else None) # Create list of scripts. Depends on OS. scripts = [ "awlsim-gui", "awlsim-client", "awlsim-server", "awlsim-symtab", "awlsim-test", ] if isWindows or fullBuild: scripts.append("awlsim-win.cmd") if not isWindows or fullBuild: scripts.append("awlsim-linuxcnc-hal") scripts.append("pilc/pilc-hat-conf") # Create freeze executable list. guiBase = None if isWindows: guiBase = "Win32GUI" freezeExecutables = [ ("awlsim-gui", None, guiBase), ("awlsim-client", None, None), ("awlsim-server", None, None), ("awlsim-symtab", None, None), ("awlsim-test", None, None), ("awlsim/coreserver/server.py", "awlsim-server-module", None), ] if py2exe: extraKeywords["console"] = [ s for s, e, b in freezeExecutables ] if cx_Freeze: executables = [] for script, exe, base in freezeExecutables: if exe: if isWindows: exe += ".exe" executables.append(Executable(script = script, targetName = exe, base = base)) else: executables.append(Executable(script = script, base = base)) extraKeywords["executables"] = executables extraKeywords["options"] = { "build_exe" : { "packages" : [ "awlsimhw_debug", "awlsimhw_dummy", "awlsim.library.iec", ], } } setup( name = "awlsim", version = VERSION_STRING, description = "S7 AWL/STL Soft-PLC", license = "GNU General Public License v2 or later", author = "Michael Buesch", author_email = "m@bues.ch", url = "https://bues.ch/a/awlsim", packages = [ "awlsim", "awlsim/common", "awlsim/core", "awlsim/core/instructions", "awlsim/core/systemblocks", "awlsim/coreclient", "awlsim/coreserver", "awlsim/gui", "awlsim/gui/icons", "awlsim/library", "awlsim/library/iec", "awlsimhw_debug", "awlsimhw_dummy", "awlsimhw_linuxcnc", "awlsimhw_pyprofibus", "awlsimhw_rpigpio", "libpilc", ], package_dir = { "libpilc" : "pilc/libpilc", }, scripts = scripts, cmdclass = cmdclass, ext_modules = ext_modules, keywords = [ "AWL", "STL", "SPS", "PLC", "Step 7", "Siemens", "emulator", "simulator", "PROFIBUS", "LinuxCNC", ], classifiers = [ "Development Status :: 4 - Beta", "Environment :: Console", "Environment :: Win32 (MS Windows)", "Environment :: X11 Applications", "Intended Audience :: Developers", "Intended Audience :: Education", "Intended Audience :: Information Technology", "Intended Audience :: Manufacturing", "Intended Audience :: Science/Research", "License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", "Operating System :: POSIX :: Linux", "Programming Language :: Cython", "Programming Language :: Python", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Programming Language :: Python :: Implementation :: Jython", "Programming Language :: Python :: Implementation :: IronPython", "Topic :: Education", "Topic :: Home Automation", "Topic :: Scientific/Engineering", "Topic :: Software Development", "Topic :: Software Development :: Interpreters", "Topic :: Software Development :: Embedded Systems", "Topic :: Software Development :: Testing", "Topic :: System :: Emulators", ], long_description = open("README.md").read(), **extraKeywords )