This repository was archived by the owner on May 21, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbuild.py
171 lines (117 loc) · 4.68 KB
/
build.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import os
import argparse
import shutil
import configparser
import glob
confPath = ".\\build.ini"
if (not os.path.isfile(confPath)):
print("cannot find build.ini file")
exit(1)
config = configparser.ConfigParser()
config.read(confPath)
# collect basic data from config file
compiler = config["build"]["compiler"]
defines = ""
for define in config["build"]["defines"].split(","):
if len(define) > 0:
defines += f"-D{define} "
rootdir = os.getcwd().replace("\\", "/")
include = os.path.join(rootdir, config["source"]["include"])
src = config["source"]["sourceRoot"]
build = config["build"]["buildFolder"]
target = config["build"]["target"]
defaultCompilerFlags = config["build"]["defaultCompilationFlags"]
linkingFlags = config["build"]["linkingFlags"]
linkedLibs = config["build"]["linkedLibs"]
ignoreCompilerFail = False
def makeDirIfNotExisting(path: str):
if (not os.path.isdir(path)):
os.makedirs(path)
args = argparse.ArgumentParser()
args.add_argument("-re", "--rebuild", help="rebuilds entire project", action="store_true")
args.add_argument("--ignore-comp-fail", help="runs old target if there were compiler errors", action="store_true")
parsed = args.parse_args()
if (parsed.rebuild):
# god, have i rebuilt everything for no reason too often
print("Are you sure you want to rebuild everything? Y/N")
answer = input()
while (not (answer in ["Y", "N"])):
answer = input()
if (answer == "Y"):
shutil.rmtree(build)
if (parsed.ignore_comp_fail):
ignoreCompilerFail = True
# make build directory if it doesn't exist
makeDirIfNotExisting(rootdir + build)
def shouldCompile(file: str):
# i am aware this is ugly.
objectFile = f"{rootdir}{build}{file}.o".replace("./", "/")
sourceFile = f"{rootdir}{src}{file}.c".replace("./", "/")
# i'm aware that this could be 1 return, this is far more readable
if not os.path.isfile(objectFile):
return True
if os.path.getmtime(objectFile) < os.path.getmtime(sourceFile):
return True
return False
# compiles source file to appropriate directory in .build, creating one if need be
def compileToObject(sourceFile: str, compileFlags: str):
print("compiling " + sourceFile.removeprefix("/"))
startingDir = os.getcwd()
os.chdir(rootdir + build)
split = sourceFile[::-1].split("/", 1)
# expected input is in the style "[\path]\file". we reverse it because there's no split last,
# split it by \ with at most 1 split and thereby get "[hatp\]". we then reverse this again
# to get \path, and use it to generate a folder and move to it if need be
if (len(split) == 2 and len(split[-1]) > 0):
subdir = split[-1][::-1]
makeDirIfNotExisting(f"{rootdir}{build}/{subdir}")
os.chdir(subdir.removeprefix("/"))
fixedFlags = compileFlags.replace("${defaultCompilationFlags}", defaultCompilerFlags)
fixedSrc = src.removeprefix(".")
command = f"{compiler} -I\"{include}\" {defines} -c \"{rootdir}{fixedSrc}{sourceFile}.c\" {fixedFlags}"
returnCode = os.system(command)
os.chdir(startingDir)
return returnCode
print("begun compiling")
compilationFailed = False
compiledAnything = False
objectFiles = []
# compile everything
for section in config.sections():
# use only sections starting in c file
if section.startswith("c file"):
# windows likes its \\ paths
def fixPath(path):
return path.replace("\\", "/")
sourceFiles = list(map(fixPath, glob.glob(config[section]["path"], recursive=True)))
# if this section has an exclude pattern, apply it
if "exclude" in config[section]:
subtracting = set(glob.glob(config[section]["exclude"], recursive=True))
sourceFiles = list(set(sourceFiles) - subtracting)
# actually compile the things
for file in sourceFiles:
# glob returns a relative path, we wnat to strip the src so we can easily compare it
# to a build pair
file = file.removeprefix(src).removesuffix(".c")
if shouldCompile(file):
errorCode = compileToObject(file, config[section]["compilationFlags"])
compiledAnything = compiledAnything or errorCode == 0
compilationFailed = compilationFailed or errorCode != 0
objectFiles.append(f"{build}{file}.o")
if compilationFailed and not ignoreCompilerFail:
print(f"compilation failed, not running {target}. use --ignore-comp-fail if you want to run anyway")
exit(1)
elif compilationFailed:
print(f"compilation failed, running {target} anyway")
elif (compiledAnything or not os.path.isfile(f"{rootdir}/{target}")):
# linking step
print("building executable")
linking = ""
for file in objectFiles:
linking += file + " "
os.system(f"{compiler} -o {target} {linking} {linkedLibs} {linkingFlags}")
print(f"executable built, running {target}")
else:
print(f"executable up to date, running {target}")
# run the thing
os.system(f"\"{os.path.join(rootdir, target)}\"")