まえがき
VB6のIDEはmake的挙動をやってくれない。つまり一連のソースコードになんの変更が無くてもコンパイルを実行すると新しいバイナリファイルが生成される。これだけならまだいいのだがコンパイルするたびに異なるバイナリファイルが生成される。つまり、2つのバイナリファイルが機能的に同一かそれとも何か変更されているかの区別が付かない。これが第一の問題。
もう一つ、よくある構成で
\---Proj
+---Common
| CommonModule.bas
|
+---PreExe
| PreExe.vbp ←CommonModule.bas参照している
|
+---MainExe
| MainExe.vbp ←CommonModule.bas参照している
|
\---PostExe
PostExe.vbp ←CommonModule.bas参照していない
こんな感じに物理ファイルレベルでソースコードを共有することは良くある。ここでCommonフォルダ内のCommonModule.basのあるメソッドを変更したとしよう。変更したからコンパイルする必要があるのはどれだろう。必要が無いのはどれだろう。片っ端から漏れなくコンパイルしても良いが必要が無いのにコンパイルすると第一の問題のように機能的変更は何もないのに異なるバイナリが生成されてしまう。
make的挙動
本家makeコマンドを使っても勿論実現できるとは思うがここではPython3でvb専用のmake的挙動を行うプログラムを書いてみる(makeなんて40年以上前からあるのにどうしてVB6の開発環境に載せんかったのか・・・)
VBのプロジェクトファイル(.vbp)を参照し下記条件のいずれかを満たす場合コンパイルを実行する。
- 実行ファイルが存在しない
- 実行ファイルよりプロジェクトファイルが新しい(例えばバージョンだけ変更したとか)
- 実行ファイルより新しいソースファイルがある
開発環境
- Python : 3.8.3
- Visual Studio Code : 1.49.2
ソースコード
プロジェクトファイル(.vbp)を扱うクラス
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()
プロジェクトファイルに記載されているソースファイル(.bas, .cls, .frm)を扱うクラス
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)
本体
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)実行ファイルが存在しない
# 2)VBPファイルが実行ファイルより新しい
# 3)実行ファイルより新しいソースファイルがある
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("コンパイル不要")
コンパイラのフルパスが2つあるのはOSやx86/X64でVB6のインストール先が異なるので。確かcompiler1の方がW10(x64)でcompiler2の方がW7(x64)だったような。
おまけ
いつもはバッチファイルを使って
py vbmake.py %1
みたいなことをするのだが、pyinstallerなるものを知ったのでexeに仕立て上げてみる。Python入ってない環境でも動かすことができるようになるのはグレイト。
>pip install pyinstaller
して
>pyinstaller vbmake.py
以上。案の定10MB超のvbmake.exeが生成された(そらそうだよね)