github.com/iDigitalFlame/xmt@v0.5.4/tools/windows_builder.py (about)

     1  #!/usr/bin/python3
     2  # Copyright (C) 2020 - 2023 iDigitalFlame
     3  #
     4  # This program is free software: you can redistribute it and/or modify
     5  # it under the terms of the GNU General Public License as published by
     6  # the Free Software Foundation, either version 3 of the License, or
     7  # any later version.
     8  #
     9  # This program is distributed in the hope that it will be useful,
    10  # but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  # GNU General Public License for more details.
    13  #
    14  # You should have received a copy of the GNU General Public License
    15  # along with this program.  If not, see <https://www.gnu.org/licenses/>.
    16  #
    17  
    18  from os import environ
    19  from random import choice
    20  from tempfile import mkdtemp
    21  from sys import stderr, exit
    22  from shutil import which, rmtree
    23  from traceback import format_exc
    24  from argparse import ArgumentParser
    25  from os.path import isdir, isfile, join
    26  from subprocess import SubprocessError, run
    27  from string import ascii_lowercase, Template
    28  
    29  C_SRC = """#define WINVER 0x0501
    30  #define _WIN32_WINNT 0x0501
    31  
    32  #define NOWH
    33  #define NOMB
    34  #define NOMSG
    35  #define NONLS
    36  #define NOMCX
    37  #define NOIME
    38  #define NOHELP
    39  #define NOCOMM
    40  #define NOICONS
    41  #define NOCRYPT
    42  #define NOKANJI
    43  #define NOSOUND
    44  #define NOCOLOR
    45  #define NOMENUS
    46  #define NOCTLMGR
    47  #define NOMINMAX
    48  #define NOSCROLL
    49  #define NODRAWTEXT
    50  #define NOMETAFILE
    51  #define NOPROFILER
    52  #define NOKEYSTATES
    53  #define NORASTEROPS
    54  #define NOCLIPBOARD
    55  #define NOWINSTYLES
    56  #define NOSYSMETRICS
    57  #define NOWINOFFSETS
    58  #define NOSHOWWINDOW
    59  #define NOTEXTMETRIC
    60  #define NOSYSCOMMANDS
    61  #define NOGDICAPMASKS
    62  #define NOWINMESSAGES
    63  #define NODEFERWINDOWPOS
    64  #define NOVIRTUALKEYCODES
    65  #define WIN32_LEAN_AND_MEAN
    66  
    67  #define UNICODE
    68  #define EXPORT __declspec(dllexport)
    69  
    70  #include <winsock.h>
    71  #include <windows.h>
    72  
    73  #include "$name.h"
    74  
    75  """
    76  C_DLL = """DWORD $thread(void) {
    77      Sleep(1000);
    78      $export();
    79      return 0;
    80  }
    81  
    82  EXPORT HRESULT WINAPI VoidFunc(void) {
    83      HANDLE c = GetConsoleWindow();
    84      if (c != NULL) {
    85          ShowWindow(c, 0);
    86      }
    87      $export();
    88  }
    89  EXPORT HRESULT WINAPI DllCanUnloadNow(void) {
    90      // Always return S_FALSE so we can stay loaded.
    91      return 1;
    92  }
    93  EXPORT HRESULT WINAPI DllRegisterServer(void) {
    94      HANDLE c = GetConsoleWindow();
    95      if (c != NULL) {
    96          ShowWindow(c, 0);
    97      }
    98      $export();
    99  }
   100  EXPORT HRESULT WINAPI DllUnregisterServer(void) {
   101      HANDLE c = GetConsoleWindow();
   102      if (c != NULL) {
   103          ShowWindow(c, 0);
   104      }
   105      $export();
   106  }
   107  EXPORT HRESULT WINAPI DllInstall(BOOL b, PCWSTR i) {
   108      HANDLE c = GetConsoleWindow();
   109      if (c != NULL) {
   110          ShowWindow(c, 0);
   111      }
   112      $export();
   113  }
   114  EXPORT HRESULT WINAPI DllGetClassObject(void* x, void *i, void* p) {
   115      HANDLE c = GetConsoleWindow();
   116      if (c != NULL) {
   117          ShowWindow(c, 0);
   118      }
   119      $export();
   120  }
   121  
   122  EXPORT VOID WINAPI $funcname(HWND h, HINSTANCE i, LPSTR a, int s) {
   123      HANDLE c = GetConsoleWindow();
   124      if (c != NULL) {
   125          ShowWindow(c, 0);
   126      }
   127      $export();
   128  }
   129  
   130  EXPORT BOOL WINAPI DllMain(HINSTANCE h, DWORD r, LPVOID args) {
   131      if (r == DLL_PROCESS_ATTACH) {
   132          DisableThreadLibraryCalls(h);
   133          CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)$thread, NULL, 0, NULL);
   134      }
   135      if (r == DLL_PROCESS_DETACH) {
   136          GenerateConsoleCtrlEvent(1, 0); // Generate a SIGTERM signal to tell Go to exit cleanly.
   137      }
   138      return TRUE;
   139  }
   140  """
   141  C_BINARY = """int main(int argc, char *argv[]) {
   142      HANDLE c = GetConsoleWindow();
   143      if (c != NULL) {
   144          ShowWindow(c, 0);
   145      }
   146      $export();
   147      return 0;
   148  }
   149  """
   150  C_COMPILER = "x86_64-w64-mingw32-gcc"
   151  C_COMPILER32 = "i686-w64-mingw32-gcc"
   152  
   153  
   154  def _main():
   155      a = ArgumentParser(description="Golang Windows DLL/EXE Build Helper")
   156      a.add_argument("-D", "--dll", dest="dll", action="store_true")
   157      a.add_argument("-3", "--32bit", dest="x32", action="store_true")
   158      a.add_argument(
   159          "-o", "--out", type=str, dest="output", metavar="output_file", required=True
   160      )
   161      a.add_argument(
   162          "-e", "--export", type=str, dest="export", metavar="export_name", required=True
   163      )
   164      a.add_argument("-r", "--root", type=str, dest="goroot", metavar="GOROOT")
   165      a.add_argument("-f", "--func", type=str, dest="func", metavar="func_name")
   166      a.add_argument("-t", "--thread", type=str, dest="thread", metavar="thread_name")
   167      a.add_argument("-l", "-ldflags", type=str, dest="flags", metavar="ldflags")
   168      a.add_argument("-g", "-gcflags", type=str, dest="gcflags", metavar="gcflags")
   169      a.add_argument("-B", "--go", type=str, dest="go", metavar="go")
   170      a.add_argument("-T", "-tags", type=str, dest="tags", metavar="tags")
   171      a.add_argument("-m", "-trimpath", dest="trim", action="store_true")
   172      a.add_argument(nargs=1, type=str, dest="input", metavar="go file")
   173  
   174      p, z = a.parse_known_intermixed_args()
   175      if len(p.input) != 1:
   176          print("Invalid input file!", file=stderr)
   177          return exit(1)
   178      p.input = p.input[0]
   179  
   180      if not isfile(p.input):
   181          print(f'"{p.input}" is not a file!', file=stderr)
   182          return exit(1)
   183      if len(p.output) == 0:
   184          print("Output path cannot be empty!", file=stderr)
   185          return exit(1)
   186      if len(p.export) == 0:
   187          print("Export function name cannot be empty!", file=stderr)
   188          return exit(1)
   189  
   190      if p.go:
   191          if not isfile(p.go):
   192              print(f'Golang path "{p.go}" not found!', file=stderr)
   193              return exit(1)
   194      else:
   195          p.go = which("go")
   196          if p.go is None:
   197              print('Golang "go" not found!', file=stderr)
   198              return exit(1)
   199      if which(C_COMPILER) is None:
   200          print(f'Compiler "{C_COMPILER}" not found!', file=stderr)
   201          return exit(1)
   202      if which(C_COMPILER32) is None:
   203          print(f'Compiler "{C_COMPILER32}" not found!', file=stderr)
   204          return exit(1)
   205  
   206      with open(p.input, "r") as f:
   207          b = f.read()
   208      if 'import "C"\n' not in b:
   209          print(f'"import "C"" must be declared in "{p.input}"!', file=stderr)
   210          return exit(1)
   211      if f"//export {p.export}\nfunc {p.export}(" not in b:
   212          print(f'Export function "{p.export}" not found in "{p.input}"!', file=stderr)
   213          return exit(1)
   214      del b
   215  
   216      e = environ
   217      if p.x32:
   218          e["CC"] = C_COMPILER32
   219      else:
   220          e["CC"] = C_COMPILER
   221      e["GOOS"] = "windows"
   222      e["CGO_ENABLED"] = "1"
   223      if isinstance(p.goroot, str) and len(p.goroot) > 0 and isdir(p.goroot):
   224          e["GOROOT"] = p.goroot
   225      if p.x32:
   226          e["GOARCH"] = "386"
   227  
   228      d = mkdtemp()
   229      print(f'Work dir "{d}"..')
   230      o = "".join([choice(ascii_lowercase) for _ in range(8)])
   231      if not isinstance(p.func, str) or len(p.func) == 0:
   232          p.func = "".join([choice(ascii_lowercase) for _ in range(12)])
   233      if not isinstance(p.thread, str) or len(p.thread) == 0:
   234          p.thread = "".join([choice(ascii_lowercase) for _ in range(12)])
   235  
   236      if p.dll:
   237          print(f'DLL: "{p.output}" entry "{p.export}" => "{p.func}/DllMain"')
   238      else:
   239          print(f'Binary: "{p.output}"')
   240  
   241      try:
   242          print(f'Building go archive "{o}.a"..')
   243          x = [p.go, "build", "-buildmode=c-archive"]
   244          if p.trim:
   245              x.append("-trimpath")
   246          if isinstance(p.tags, str) and len(p.tags) > 0:
   247              x += ["-tags", p.tags]
   248          if isinstance(p.flags, str) and len(p.flags) > 0:
   249              x += ["-ldflags", p.flags]
   250          if isinstance(p.gcflags, str) and len(p.gcflags) > 0:
   251              x += ["-gcflags", p.gcflags]
   252          x += ["-o", f"{join(d, o)}.a"]
   253  
   254          if isinstance(z, list) and len(z) > 0:
   255              x += z
   256          x.append(p.input)
   257          run(x, env=e, text=True, check=True, capture_output=True)
   258          del x
   259  
   260          print(f'Creating C stub "{o}.c"..')
   261          with open(f"{join(d, o)}.c", "w") as f:
   262              f.write(Template(C_SRC).substitute(name=o))
   263              if p.dll:
   264                  f.write(
   265                      Template(C_DLL).substitute(
   266                          export=p.export,
   267                          thread=p.thread,
   268                          funcname=p.func,
   269                      )
   270                  )
   271              else:
   272                  f.write(Template(C_BINARY).substitute(export=p.export))
   273          if not p.dll:
   274              return run(
   275                  [
   276                      e["CC"],
   277                      "-mwindows",
   278                      "-o",
   279                      p.output,
   280                      "-fPIC",
   281                      "-pthread",
   282                      "-lwinmm",
   283                      "-lntdll",
   284                      "-lws2_32",
   285                      "-Wa,--strip-local-absolute",
   286                      "-Wp,-femit-struct-debug-reduced,-O2",
   287                      "-Wl,-x,-s,-nostdlib,--no-insert-timestamp",
   288                      f"{join(d, o)}.c",
   289                      f"{join(d, o)}.a",
   290                  ],
   291                  env=e,
   292                  text=True,
   293                  check=True,
   294                  capture_output=True,
   295              )
   296          run(
   297              [
   298                  e["CC"],
   299                  "-c",
   300                  "-o",
   301                  f"{join(d, o)}.o",
   302                  "-mwindows",
   303                  "-fPIC",
   304                  "-pthread",
   305                  "-lwinmm",
   306                  "-lntdll",
   307                  "-lws2_32",
   308                  "-Wa,--strip-local-absolute",
   309                  "-Wp,-femit-struct-debug-reduced,-O2",
   310                  "-Wl,-x,-s,-nostdlib,--no-insert-timestamp",
   311                  f"{join(d, o)}.c",
   312              ],
   313              cwd=d,
   314              env=e,
   315              text=True,
   316              check=True,
   317              capture_output=True,
   318          )
   319          run(
   320              [
   321                  e["CC"],
   322                  "-shared",
   323                  "-o",
   324                  p.output,
   325                  "-mwindows",
   326                  "-fPIC",
   327                  "-pthread",
   328                  "-lwinmm",
   329                  "-lntdll",
   330                  "-lws2_32",
   331                  "-Wa,--strip-local-absolute",
   332                  "-Wp,-femit-struct-debug-reduced,-O2",
   333                  "-Wl,-x,-s,-nostdlib,--no-insert-timestamp",
   334                  f"{join(d, o)}.o",
   335                  f"{join(d, o)}.a",
   336              ],
   337              cwd=d,
   338              env=e,
   339              text=True,
   340              check=True,
   341              capture_output=True,
   342          )
   343      finally:
   344          rmtree(d)
   345          del d, e
   346  
   347  
   348  if __name__ == "__main__":
   349      try:
   350          _main()
   351      except SubprocessError as err:
   352          print(f"Err: {err.stderr} {err.stdout}\n{format_exc(3)}", file=stderr)
   353      except Exception as err:
   354          print(f"Error: {err}\n{format_exc(3)}", file=stderr)
   355          exit(1)