[PYTHON] Make for VB6.

Preface

VB6 [IDE](https://ja.wikipedia.org/wiki/%E7%B5%B1%E5%90%88%E9%96%8B%E7%99%BA%E7%92%B0%E5 % A2% 83) does not behave like make. In other words, a new binary file will be generated when you compile without any changes to the source code. That's fine, but each time you compile, a different binary file is generated. That is, it is indistinguishable whether the two binary files are functionally identical or have been modified. This is the first problem.

Another common configuration

\---Proj
  +---Common
  |     CommonModule.bas
  |
  +---PreExe
  |     PreExe.vbp ←CommonModule.bas referencing
  |
  +---MainExe
  |     MainExe.vbp ←CommonModule.bas referencing
  |
  \---PostExe
        PostExe.vbp ←CommonModule.bas not referenced

It's common to share source code at the physical file level like this. Now suppose you change a method in CommonModule.bas in the Common folder. Which one needs to be compiled because I changed it? Which one isn't necessary? You can compile everything from one end to the next, but if you compile it without needing it, a different binary will be generated even though there are no functional changes like the first problem.

make-like behavior

make

Of course I think that it can be realized by using the original make command, but here I will write a program that performs vb-specific make-like behavior in Python 3 (make has been around for over 40 years, so why not put it in the VB6 development environment? Was it ...)

Refer to the VB project file (.vbp) and compile if any of the following conditions are met.

--Executable file does not exist --The project file is newer than the executable file (for example, only the version has been changed) --There is a source file newer than the executable file

Development environment

Source code

Class that handles project files (.vbp)

projectfile.py


import child
import os

class ProjectFile:
    __lines = []
    __exe_name32 = ""
    __children = []

    def get_exe_name32(self):
        line = [i for i in self.__lines if i.startswith("ExeName32=")]

        if len(line) != 0:
            (_, value) = line[0].split('=')
            self.exe_name32 = value.replace('\"', '').replace('\n', '')

    def get_children(self):
        keys = ["Clas", "Form", "Modu"]

        servived_lines = [i for i in self.__lines if i[0:4] in keys]

        for line in servived_lines:
            c = child.Child(line, os.path.dirname(self.fullpath))
            self.__children.append(c)

    @property
    def exe_name32(self):
        return self.__exe_name32

    @exe_name32.setter
    def exe_name32(self, value):
        self.__exe_name32 = value

    @property
    def children(self):
        return self.__children

    def __init__(self, fullpath):
        self.fullpath = fullpath
        with open(fullpath, mode='r') as f:
            self.__lines = f.readlines()
        self.get_exe_name32()
        self.get_children()

Class that handles the source files (.bas, .cls, .frm) described in the project file

child.py


import os
import datetime

class Child:
    __full_path = ""
    __last_write_time = ""

    @property
    def full_path(self):
        return self.__full_path

    @property
    def last_write_time(self):
        return self.__last_write_time

    def __init__(self, line, basepath):
        (_, value) = line.split('=')

        if (';' in value):
            (_, item) = value.split(';')
            filename = item.strip()
        else:
            filename = value.strip()

        self.__full_path = os.path.join(basepath, filename)
        self.__last_write_time = os.path.getmtime(self.__full_path)

Body

vbmake.py


import projectfile
import sys
import os
import datetime
import subprocess

if __name__ == "__main__":
    if (len(sys.argv) != 2):
        print("vbmake.py vbpfullpath")
        x = input()
        exit

    vbp_full_path = sys.argv[1]
    vbp = projectfile.ProjectFile(vbp_full_path)
    exe_full_path = os.path.join(os.path.dirname(vbp_full_path), vbp.exe_name32)

    if os.path.exists(exe_full_path):
        standard_time_stamp = os.path.getmtime(exe_full_path)
        print(f"TimeStamp={datetime.datetime.fromtimestamp(standard_time_stamp)}")

        exe_not_found = False
        is_new_vbp = os.path.getmtime(vbp_full_path) > standard_time_stamp
        are_new_sources = any([i.last_write_time > standard_time_stamp for i in vbp.children])
    else:
        exe_not_found = True

    # 1)Executable does not exist
    # 2)VBP file is newer than the executable file
    # 3)There is a source file newer than the executable file
    if exe_not_found or is_new_vbp or are_new_sources:
        compiler1 = "C:\\Program Files\\Microsoft Visual Studio\\VB98\\VB6.EXE"
        compiler2 = "C:\\Program Files (x86)\\Microsoft Visual Studio\\VB98\\VB6.EXE"
        option = "/make"

        if os.path.exists(compiler1):
            compiler = compiler1
        else:
            compiler = compiler2

        print(f"{compiler} {option} {vbp_full_path}")
        subprocess.run([compiler, option, vbp_full_path])
    else:
        print("No compilation required")

There are two full paths of the compiler because the installation destination of VB6 is different depending on the OS and x86 / X64. It seems that compiler1 was W10 (x64) and compiler2 was W7 (x64).

bonus

I usually use a batch file

make.bat


py vbmake.py %1

I do something like this, but I found out what is called pyinstaller, so I will make it into an exe. It is great that it can be operated even in an environment without Python.

>pip install pyinstaller

do it

>pyinstaller vbmake.py

that's all. Sure enough, vbmake.exe over 10MB was generated (that's right)

Recommended Posts

Make for VB6.
Make a Tweet box for Pepper
Make Qt for Python app a desktop app
ROS course 107 Make a client for rosblidge
Make a chessboard pattern for camera calibration
Let's make a Backend plugin for Errbot
How to make Spigot plugin (for Java beginners)
Make a histogram for the time being (matplotlib)
Let's make a module for Python using SWIG
How to make Python faster for beginners [numpy]