Initial commit
This commit is contained in:
commit
1571f570bf
6 changed files with 281 additions and 0 deletions
151
umake.py
Executable file
151
umake.py
Executable file
|
|
@ -0,0 +1,151 @@
|
|||
#! python3
|
||||
|
||||
from argparse import ArgumentParser
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import shutil
|
||||
import sys
|
||||
from typing import List
|
||||
|
||||
|
||||
class Config:
|
||||
CC = 'g++'
|
||||
APPS = ['app_name']
|
||||
JOB_COUNT = 1
|
||||
|
||||
BIN_DIR = 'bin'
|
||||
INCLUDE_DIR = 'include'
|
||||
OBJECT_DIR = 'obj'
|
||||
SOURCE_DIR = 'src'
|
||||
|
||||
COMMON_FLAGS = '-std=c++17'
|
||||
COMMON_DEBUG_FLAGS = '-g'
|
||||
COMMON_RELEASE_FLAGS = '-O2 -flto'
|
||||
COMPILE_FLAGS = f'-Wall -I{INCLUDE_DIR}'
|
||||
LINK_FLAGS = ''
|
||||
|
||||
CPP_HEADERS = []
|
||||
CPP_SOURCES = []
|
||||
|
||||
|
||||
def get_hash(path: str) -> str:
|
||||
with open(path, 'r') as hashing_file:
|
||||
hash_obj = hashlib.md5()
|
||||
hash_obj.update(hashing_file.read().encode())
|
||||
return hash_obj.hexdigest()
|
||||
|
||||
|
||||
def make(config: Config):
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument('-j', type=int, default=config.JOB_COUNT, help='Jobs count (multi-processing)')
|
||||
parser.add_argument('--type', default='production', help='Compilation type (release, debug). Default=release')
|
||||
parser.add_argument('--clean', action='store_true', help='Clean all file instead of building')
|
||||
arguments = parser.parse_args()
|
||||
|
||||
# Clean action
|
||||
if arguments.clean:
|
||||
shutil.rmtree(config.OBJECT_DIR, ignore_errors=True)
|
||||
shutil.rmtree(config.BIN_DIR, ignore_errors=True)
|
||||
return
|
||||
|
||||
# Update flags and directories for mode debug/release
|
||||
if arguments.type == 'debug':
|
||||
config.COMMON_FLAGS += ' ' + config.COMMON_DEBUG_FLAGS
|
||||
config.OBJECT_DIR = os.path.join(config.OBJECT_DIR, 'debug')
|
||||
else:
|
||||
config.COMMON_FLAGS += ' ' + config.COMMON_RELEASE_FLAGS
|
||||
config.OBJECT_DIR = os.path.join(config.OBJECT_DIR, 'release')
|
||||
|
||||
# Create list of source to process (tuple[source_path, object_path])
|
||||
compile_list = [(source_file, source_file[:-4].replace(config.SOURCE_DIR, config.OBJECT_DIR) + '.o')
|
||||
for source_file in config.CPP_SOURCES]
|
||||
todo_list = []
|
||||
|
||||
hash_dict = {}
|
||||
if os.path.exists(os.path.join(config.OBJECT_DIR, 'hash.json')):
|
||||
with open(os.path.join(config.OBJECT_DIR, 'hash.json'), 'r') as hash_file:
|
||||
hash_dict = json.loads(hash_file.read())
|
||||
|
||||
# Check header files (if any modification is done all source file will be processed to avoid any problem)
|
||||
header_changed = False
|
||||
header_hash_dict = {}
|
||||
for header_path in config.CPP_HEADERS:
|
||||
header_hash = get_hash(header_path)
|
||||
if header_path not in hash_dict or hash_dict[header_path] != header_hash:
|
||||
header_hash_dict[header_path] = header_hash
|
||||
header_changed = True
|
||||
|
||||
# Check source file and generate compilation commands to execute
|
||||
for source_path, object_path in compile_list:
|
||||
cmd = ' '.join([config.CC, config.COMMON_FLAGS, config.COMPILE_FLAGS, source_path, '-c', '-o', object_path])
|
||||
if not os.path.exists(os.path.dirname(object_path)):
|
||||
os.makedirs(os.path.dirname(object_path))
|
||||
source_hash = get_hash(source_path)
|
||||
if (header_changed or not os.path.exists(object_path)
|
||||
or source_path not in hash_dict or hash_dict[source_path] != source_hash):
|
||||
todo_list.append((source_path, source_hash, cmd))
|
||||
continue
|
||||
|
||||
# Running compilation processes
|
||||
if not os.path.exists(config.OBJECT_DIR):
|
||||
os.makedirs(config.OBJECT_DIR)
|
||||
error_path = None
|
||||
if arguments.j > 1: # Multi-process
|
||||
jobs: List[subprocess.Popen] = []
|
||||
for source_path, source_hash, cmd in todo_list:
|
||||
print(cmd)
|
||||
jobs.insert(0, (source_path, source_hash, subprocess.Popen(cmd, shell=True))) # FIFO style (will be poped)
|
||||
if len(jobs) >= arguments.j: # If jobs count is maxed we wait for the oldest one to finished
|
||||
job_path, job_hash, oldest_job = jobs.pop()
|
||||
oldest_job.wait()
|
||||
if oldest_job.returncode != 0:
|
||||
error_path = job_path
|
||||
break
|
||||
hash_dict[job_path] = job_hash # Update hash if no error
|
||||
if error_path is None:
|
||||
for job_path, job_hash, job in jobs: # Wait the last jobs to finish
|
||||
job.wait()
|
||||
if job.returncode != 0:
|
||||
error_path = job_path
|
||||
break
|
||||
hash_dict[job_path] = job_hash # Update hash if no error
|
||||
else: # Single-process
|
||||
for source_path, source_hash, cmd in todo_list:
|
||||
print(cmd)
|
||||
complete = subprocess.run(cmd, check=False, shell=True)
|
||||
if complete.returncode != 0:
|
||||
error_path = source_path
|
||||
break
|
||||
hash_dict[source_path] = source_hash # Update hash if no error
|
||||
if error_path:
|
||||
print(f'Error compiling {error_path}')
|
||||
with open(os.path.join(config.OBJECT_DIR, 'hash.json'), 'w') as hash_file:
|
||||
hash_file.write(json.dumps(hash_dict, indent=1))
|
||||
sys.exit(1)
|
||||
|
||||
# Running linking processes
|
||||
if not os.path.exists(config.BIN_DIR):
|
||||
os.makedirs(config.BIN_DIR)
|
||||
for app_name in config.APPS:
|
||||
app_path = os.path.join(config.BIN_DIR, app_name)
|
||||
app_objects = [file_name + '.o' for file_name in config.APPS if file_name != app_name]
|
||||
object_files = [object_path for _, object_path in compile_list
|
||||
if os.path.basename(object_path) not in app_objects]
|
||||
cmd = ' '.join([config.CC, config.COMMON_FLAGS, *object_files, '-o', app_path, config.LINK_FLAGS])
|
||||
print(cmd)
|
||||
complete = subprocess.run(cmd, check=False, shell=True)
|
||||
if complete.returncode != 0:
|
||||
error_path = app_name
|
||||
if error_path:
|
||||
with open(os.path.join(config.OBJECT_DIR, 'hash.json'), 'w') as hash_file:
|
||||
hash_file.write(json.dumps(hash_dict, indent=1))
|
||||
print('Error linking f{error_path}')
|
||||
sys.exit(1)
|
||||
|
||||
# Updating header hashes only if everything compiled and linked correctly
|
||||
for header_path in header_hash_dict:
|
||||
hash_dict[header_path] = header_hash_dict[header_path]
|
||||
with open(os.path.join(config.OBJECT_DIR, 'hash.json'), 'w') as hash_file:
|
||||
hash_file.write(json.dumps(hash_dict, indent=1))
|
||||
Loading…
Add table
Add a link
Reference in a new issue