This repository has been archived on 2026-07-19. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.
acquire-core-full/generate.py

2254 lines
64 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
generate.py - acquire-core-full 레거시 C 코퍼스 결정론적 생성기
================================================================
카드 매입·정산 도메인의 TxCore(사내 TP 프레임워크) 기반 레거시 C 시스템을
아키타입별 템플릿 × 파라미터(module, program-id, difficulty tier)로 생성한다.
- 각 프로그램은 self-contained: framework/txcore(공유 라이브러리)만 의존하며
독립적으로 .o 로 컴파일된다.
- 산출: framework/, app/{online,batch,dbio,common,include,shared}, fml/, scripts/,
mk/, config/, db/, Makefile, inventory.json
사용법:
python3 generate.py [--fraction F] # F=1.0 full(~2050 files), 0.05 quick test
"""
import argparse
import json
import os
import shutil
ROOT = os.path.dirname(os.path.abspath(__file__))
# 11개 업무 모듈: code -> (한글명, 영문 transform 태그)
MODULES = [
("mg", "전문게이트웨이", "msg-gateway"),
("au", "승인한도", "authorization"),
("ac", "매입", "acquire"),
("rc", "대사", "reconciliation"),
("vl", "정합성", "validation"),
("st", "정산수수료", "settlement"),
("py", "지급", "payment"),
("lg", "원장", "ledger"),
("mm", "마스터", "master"),
("cm", "공통", "common"),
("cl", "마감", "closing"),
]
MOD_CODES = [m[0] for m in MODULES]
MOD_KNAME = {m[0]: m[1] for m in MODULES}
MOD_XFORM = {m[0]: m[2] for m in MODULES}
# 파일 타입별 목표 건수 (fraction=1.0 기준)
TARGETS = {
"online": 350,
"batch": 180,
"common": 120,
"dbio": 300, # 이 중 11개는 모듈 표준 dbio_main
"dbio_sql": 50,
"headers": 600, # core(11)+io헤더+cu헤더+standalone
"fml": 120,
"sh": 180,
"mk": 90,
"config": 60,
}
# 데모 링크 대상 (생성 산출물로 링크 검증)
ONLINE_DEMOS = ["mg", "ac", "rc", "st"]
BATCH_DEMOS = ["rc", "cl"]
def tier_for(i):
"""~55% easy, ~30% medium, ~15% hard (결정론적)."""
r = i % 20
if r < 11:
return "easy"
if r < 17:
return "medium"
return "hard"
def render(tpl, **kw):
out = tpl
for k, v in kw.items():
out = out.replace("@" + k + "@", str(v))
return out
# ======================================================================
# 공유 프레임워크 소스 (검증된 슬라이스에서 그대로 이식) - raw 문자열
# ======================================================================
TXCORE_H = r'''/*
* txcore.h - TxCore 공통 프레임워크 API
*
* TxCore 는 사내 표준 공통 프레임워크로, Tuxedo/ProFrame 계열 TP 모니터의
* ATMI(tpcall/tpreturn/tpbegin) 및 FML/UBF(Fchg/Fget) 관용구를 얇게 감싼
* 자체 구현 계층이다. 외부 TP 미들웨어 설치 없이 단독 빌드/링크된다.
*/
#ifndef TXCORE_H
#define TXCORE_H
#include <stddef.h>
#include <stdarg.h>
#ifdef __cplusplus
extern "C" {
#endif
#define TX_OK 0
#define TX_FAIL (-1)
#define TX_ENOENT (-2)
#define TX_EINVAL (-3)
#define TX_EDB (-4)
#define TX_ENOMEM (-5)
#define TX_ENOKEY (-6)
#define TX_LOG_DEBUG 0
#define TX_LOG_INFO 1
#define TX_LOG_WARN 2
#define TX_LOG_ERROR 3
#define TX_MAX_SLOTS 64
#define TX_KEY_LEN 32
#define TX_VAL_LEN 256
typedef struct {
char key[TX_KEY_LEN];
char val[TX_VAL_LEN];
int len;
int used;
} TXFIELD;
typedef struct {
int count;
TXFIELD slots[TX_MAX_SLOTS];
} TXBUF;
typedef struct {
char name[TX_KEY_LEN];
TXBUF *in;
TXBUF *out;
int rcode;
long xid;
} TXSVCINFO;
typedef void (*tx_service_fn)(TXSVCINFO *ctx);
#define TX_SERVICE(name, ctx) void name(TXSVCINFO *ctx)
TXBUF *tx_buf_alloc(void);
void tx_buf_free(TXBUF *buf);
void tx_buf_reset(TXBUF *buf);
int tx_buf_set(TXBUF *buf, const char *key, const char *val, int len);
int tx_buf_sets(TXBUF *buf, const char *key, const char *val);
int tx_buf_setlong(TXBUF *buf, const char *key, long val);
int tx_buf_get(TXBUF *buf, const char *key, char *out, int outlen);
int tx_buf_getlong(TXBUF *buf, const char *key, long *out);
int tx_register(const char *name, tx_service_fn fn);
int tx_call(const char *svc, TXBUF *in, TXBUF *out);
int tx_return(TXSVCINFO *ctx, int rc, TXBUF *out);
int tx_service_count(void);
int tx_begin(void);
int tx_commit(void);
int tx_abort(void);
long tx_current_xid(void);
void tx_log(int level, const char *fmt, ...);
void tx_set_loglevel(int level);
const char *tx_strerror(int rc);
#ifdef __cplusplus
}
#endif
#endif /* TXCORE_H */
'''
TXCORE_DBIO_H = r'''/*
* txcore_dbio.h - TxCore DBIO 규약 (ECPG/Pro*C 공통)
*/
#ifndef TXCORE_DBIO_H
#define TXCORE_DBIO_H
#include "txcore.h"
#define TX_SQL_OK 0
#define TX_SQL_NOTFOUND 100
#define TX_DBIO_RESULT(sc) \
((sc) == TX_SQL_OK ? TX_OK : \
(sc) == TX_SQL_NOTFOUND ? TX_ENOENT : TX_EDB)
#define TX_DBIO_LOG_ERR(tag) \
tx_log(TX_LOG_ERROR, "DBIO 오류 [%s] sqlcode=%ld msg=%.*s", \
(tag), (long)sqlca.sqlcode, \
(int)sqlca.sqlerrm.sqlerrml, sqlca.sqlerrm.sqlerrmc)
#endif /* TXCORE_DBIO_H */
'''
TXCORE_C = r'''/*
* txcore.c - TxCore 공통 프레임워크 구현
*/
#include "txcore.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define TX_MAX_SERVICES 512
typedef struct {
char name[TX_KEY_LEN];
tx_service_fn fn;
} tx_entry_t;
static tx_entry_t g_registry[TX_MAX_SERVICES];
static int g_registry_count = 0;
static int g_loglevel = TX_LOG_INFO;
static long g_xid_seq = 0;
static long g_cur_xid = 0;
static const char *level_name(int level)
{
switch (level) {
case TX_LOG_DEBUG: return "DEBUG";
case TX_LOG_INFO: return "INFO ";
case TX_LOG_WARN: return "WARN ";
case TX_LOG_ERROR: return "ERROR";
default: return "?????";
}
}
void tx_set_loglevel(int level) { g_loglevel = level; }
void tx_log(int level, const char *fmt, ...)
{
va_list ap;
time_t now;
struct tm tmv;
char ts[20];
if (level < g_loglevel)
return;
now = time(NULL);
localtime_r(&now, &tmv);
strftime(ts, sizeof(ts), "%Y-%m-%d %H:%M:%S", &tmv);
fprintf(stderr, "[%s] %s ", ts, level_name(level));
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fputc('\n', stderr);
}
const char *tx_strerror(int rc)
{
switch (rc) {
case TX_OK: return "정상";
case TX_FAIL: return "일반 실패";
case TX_ENOENT: return "서비스 미등록";
case TX_EINVAL: return "파라미터 오류";
case TX_EDB: return "DB 오류";
case TX_ENOMEM: return "자원 부족";
case TX_ENOKEY: return "버퍼 키 없음";
default: return "알 수 없는 오류";
}
}
TXBUF *tx_buf_alloc(void) { return (TXBUF *)calloc(1, sizeof(TXBUF)); }
void tx_buf_free(TXBUF *buf) { if (buf) free(buf); }
void tx_buf_reset(TXBUF *buf) { if (buf) memset(buf, 0, sizeof(*buf)); }
static TXFIELD *find_slot(TXBUF *buf, const char *key)
{
int i;
for (i = 0; i < TX_MAX_SLOTS; i++) {
if (buf->slots[i].used &&
strncmp(buf->slots[i].key, key, TX_KEY_LEN) == 0)
return &buf->slots[i];
}
return NULL;
}
static TXFIELD *alloc_slot(TXBUF *buf, const char *key)
{
int i;
TXFIELD *f = find_slot(buf, key);
if (f)
return f;
for (i = 0; i < TX_MAX_SLOTS; i++) {
if (!buf->slots[i].used) {
f = &buf->slots[i];
memset(f, 0, sizeof(*f));
strncpy(f->key, key, TX_KEY_LEN - 1);
f->used = 1;
buf->count++;
return f;
}
}
return NULL;
}
int tx_buf_set(TXBUF *buf, const char *key, const char *val, int len)
{
TXFIELD *f;
if (!buf || !key || !val)
return TX_EINVAL;
if (len < 0 || len >= TX_VAL_LEN)
return TX_ENOMEM;
f = alloc_slot(buf, key);
if (!f)
return TX_ENOMEM;
memcpy(f->val, val, len);
f->val[len] = '\0';
f->len = len;
return TX_OK;
}
int tx_buf_sets(TXBUF *buf, const char *key, const char *val)
{
if (!val)
return TX_EINVAL;
return tx_buf_set(buf, key, val, (int)strlen(val));
}
int tx_buf_setlong(TXBUF *buf, const char *key, long val)
{
char tmp[32];
snprintf(tmp, sizeof(tmp), "%ld", val);
return tx_buf_set(buf, key, tmp, (int)strlen(tmp));
}
int tx_buf_get(TXBUF *buf, const char *key, char *out, int outlen)
{
TXFIELD *f;
if (!buf || !key || !out || outlen <= 0)
return TX_EINVAL;
f = find_slot(buf, key);
if (!f)
return TX_ENOKEY;
if (f->len >= outlen)
return TX_ENOMEM;
memcpy(out, f->val, f->len);
out[f->len] = '\0';
return TX_OK;
}
int tx_buf_getlong(TXBUF *buf, const char *key, long *out)
{
TXFIELD *f;
char *end;
long v;
if (!buf || !key || !out)
return TX_EINVAL;
f = find_slot(buf, key);
if (!f)
return TX_ENOKEY;
v = strtol(f->val, &end, 10);
if (end == f->val)
return TX_EINVAL;
*out = v;
return TX_OK;
}
int tx_register(const char *name, tx_service_fn fn)
{
int i;
if (!name || !fn)
return TX_EINVAL;
if (g_registry_count >= TX_MAX_SERVICES)
return TX_ENOMEM;
for (i = 0; i < g_registry_count; i++) {
if (strncmp(g_registry[i].name, name, TX_KEY_LEN) == 0) {
g_registry[i].fn = fn;
return TX_OK;
}
}
strncpy(g_registry[g_registry_count].name, name, TX_KEY_LEN - 1);
g_registry[g_registry_count].fn = fn;
g_registry_count++;
tx_log(TX_LOG_DEBUG, "서비스 등록: %s", name);
return TX_OK;
}
int tx_service_count(void) { return g_registry_count; }
static tx_service_fn lookup(const char *name)
{
int i;
for (i = 0; i < g_registry_count; i++) {
if (strncmp(g_registry[i].name, name, TX_KEY_LEN) == 0)
return g_registry[i].fn;
}
return NULL;
}
int tx_call(const char *svc, TXBUF *in, TXBUF *out)
{
tx_service_fn fn;
TXSVCINFO ctx;
if (!svc || !in || !out)
return TX_EINVAL;
fn = lookup(svc);
if (!fn) {
tx_log(TX_LOG_ERROR, "tx_call: 서비스 미등록 svc=%s", svc);
return TX_ENOENT;
}
memset(&ctx, 0, sizeof(ctx));
strncpy(ctx.name, svc, TX_KEY_LEN - 1);
ctx.in = in;
ctx.out = out;
ctx.rcode = TX_OK;
ctx.xid = g_cur_xid;
fn(&ctx);
return ctx.rcode;
}
int tx_return(TXSVCINFO *ctx, int rc, TXBUF *out)
{
if (!ctx)
return TX_EINVAL;
ctx->rcode = rc;
if (out && ctx->out && out != ctx->out)
memcpy(ctx->out, out, sizeof(TXBUF));
return rc;
}
int tx_begin(void)
{
if (g_cur_xid != 0) {
tx_log(TX_LOG_WARN, "tx_begin: 이미 진행중 xid=%ld", g_cur_xid);
return TX_FAIL;
}
g_cur_xid = ++g_xid_seq;
tx_log(TX_LOG_INFO, "tx_begin xid=%ld", g_cur_xid);
return TX_OK;
}
int tx_commit(void)
{
if (g_cur_xid == 0) {
tx_log(TX_LOG_WARN, "tx_commit: 트랜잭션 없음");
return TX_FAIL;
}
tx_log(TX_LOG_INFO, "tx_commit xid=%ld", g_cur_xid);
g_cur_xid = 0;
return TX_OK;
}
int tx_abort(void)
{
if (g_cur_xid == 0) {
tx_log(TX_LOG_WARN, "tx_abort: 트랜잭션 없음");
return TX_FAIL;
}
tx_log(TX_LOG_WARN, "tx_abort xid=%ld", g_cur_xid);
g_cur_xid = 0;
return TX_OK;
}
long tx_current_xid(void) { return g_cur_xid; }
'''
MSG_LAYOUT_H = r'''/*
* msg_layout.h - 카드 매입 전문(電文) 고정길이 레이아웃 (공유)
*/
#ifndef MSG_LAYOUT_H
#define MSG_LAYOUT_H
#define MSG_TYPE_RECV "0200"
#define MSG_TYPE_RESP "0210"
#define MSG_TYPE_RECON "0500"
#define RESP_OK "0000"
#define RESP_INVALID "9001"
#define RESP_NOMERCH "9002"
#define RESP_SYSERR "9999"
typedef struct {
char msg_type[4];
char trans_code[6];
char merch_id[15];
char card_no[16];
char approval_no[9];
char amount[12];
char txn_date[8];
char txn_time[6];
char inst_month[2];
char resp_code[4];
char filler[18];
} acq_msg_t;
#define MSG_ACQ_LEN ((int)sizeof(acq_msg_t))
#define FLD_MSG_TYPE_LEN 4
#define FLD_TRANS_CODE_LEN 6
#define FLD_MERCH_ID_LEN 15
#define FLD_CARD_NO_LEN 16
#define FLD_APPROVAL_LEN 9
#define FLD_AMOUNT_LEN 12
#define FLD_TXN_DATE_LEN 8
#define FLD_TXN_TIME_LEN 6
#define FLD_INST_LEN 2
#define FLD_RESP_LEN 4
#endif /* MSG_LAYOUT_H */
'''
ACQ_UTIL_H = r'''/*
* acq_util.h - 공통 유틸리티 (일자/금액/전문) 선언 (공유)
*/
#ifndef ACQ_UTIL_H
#define ACQ_UTIL_H
#include "msg_layout.h"
int date_is_valid(const char *yyyymmdd);
int date_weekday(const char *yyyymmdd);
int date_is_weekend(const char *yyyymmdd);
int date_next_business(const char *yyyymmdd, char *out);
void date_today(char *out);
long amount_parse(const char *field, int len);
int amount_format(long amount, char *out, int len);
void amount_format_won(long amount, char *out, int outlen);
int amount_is_valid(long amount);
void msg_field_copy(char *out, const char *field, int len);
void msg_field_set(char *field, const char *src, int len);
void msg_field_set_num(char *field, const char *src, int len);
int msg_unpack(acq_msg_t *msg, const char *raw, int rawlen);
int msg_pack(const acq_msg_t *msg, char *raw, int rawlen);
int msg_validate(const acq_msg_t *msg);
#endif /* ACQ_UTIL_H */
'''
UTIL_DATE_C = r'''/*
* util_date.c - 일자/영업일 유틸리티 (plain C, 공유)
*/
#include "acq_util.h"
#include <string.h>
#include <ctype.h>
#include <stdio.h>
#include <time.h>
static int is_all_digit(const char *s, int len)
{
int i;
for (i = 0; i < len; i++)
if (!isdigit((unsigned char)s[i]))
return 0;
return 1;
}
static int to_int(const char *s, int len)
{
int i, v = 0;
for (i = 0; i < len; i++)
v = v * 10 + (s[i] - '0');
return v;
}
static int days_in_month(int y, int m)
{
static const int d[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
if (m < 1 || m > 12)
return 0;
if (m == 2) {
int leap = (y % 4 == 0 && y % 100 != 0) || (y % 400 == 0);
return leap ? 29 : 28;
}
return d[m - 1];
}
int date_is_valid(const char *yyyymmdd)
{
int y, m, d;
if (!yyyymmdd || strlen(yyyymmdd) < 8)
return 0;
if (!is_all_digit(yyyymmdd, 8))
return 0;
y = to_int(yyyymmdd, 4);
m = to_int(yyyymmdd + 4, 2);
d = to_int(yyyymmdd + 6, 2);
if (y < 1900 || y > 2999) return 0;
if (m < 1 || m > 12) return 0;
if (d < 1 || d > days_in_month(y, m)) return 0;
return 1;
}
int date_weekday(const char *yyyymmdd)
{
static const int t[] = { 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4 };
int y, m, d;
if (!date_is_valid(yyyymmdd))
return -1;
y = to_int(yyyymmdd, 4);
m = to_int(yyyymmdd + 4, 2);
d = to_int(yyyymmdd + 6, 2);
if (m < 3)
y -= 1;
return (y + y / 4 - y / 100 + y / 400 + t[m - 1] + d) % 7;
}
int date_is_weekend(const char *yyyymmdd)
{
int w = date_weekday(yyyymmdd);
return (w == 0 || w == 6) ? 1 : 0;
}
static void add_one_day(const char *in, char *out)
{
int y = to_int(in, 4);
int m = to_int(in + 4, 2);
int d = to_int(in + 6, 2);
d++;
if (d > days_in_month(y, m)) {
d = 1; m++;
if (m > 12) { m = 1; y++; }
}
snprintf(out, 9, "%04d%02d%02d", y, m, d);
}
int date_next_business(const char *yyyymmdd, char *out)
{
char cur[9];
int guard = 0;
if (!date_is_valid(yyyymmdd) || !out)
return -1;
memcpy(cur, yyyymmdd, 8);
cur[8] = '\0';
do {
add_one_day(cur, cur);
if (++guard > 14)
return -1;
} while (date_is_weekend(cur));
memcpy(out, cur, 8);
out[8] = '\0';
return 0;
}
void date_today(char *out)
{
time_t now = time(NULL);
struct tm tmv;
localtime_r(&now, &tmv);
strftime(out, 9, "%Y%m%d", &tmv);
}
'''
UTIL_AMOUNT_C = r'''/*
* util_amount.c - 금액/통화 유틸리티 (plain C, 공유)
*/
#include "acq_util.h"
#include <string.h>
#include <ctype.h>
#include <stdio.h>
#define AMOUNT_MAX 100000000L
long amount_parse(const char *field, int len)
{
long v = 0;
int i;
if (!field || len <= 0)
return -1;
for (i = 0; i < len; i++) {
char c = field[i];
if (c == ' ')
continue;
if (!isdigit((unsigned char)c))
return -1;
v = v * 10 + (c - '0');
}
return v;
}
int amount_format(long amount, char *out, int len)
{
char tmp[32];
int n;
if (!out || len <= 0 || amount < 0)
return -1;
n = snprintf(tmp, sizeof(tmp), "%0*ld", len, amount);
if (n < 0 || n > len)
return -1;
memcpy(out, tmp, len);
return 0;
}
void amount_format_won(long amount, char *out, int outlen)
{
char raw[32];
int n, i, j, digits, first;
if (!out || outlen <= 0)
return;
n = snprintf(raw, sizeof(raw), "%ld", amount);
if (n < 0) { out[0] = '\0'; return; }
first = (raw[0] == '-') ? 1 : 0;
digits = n - first;
j = 0;
if (first && j < outlen - 1)
out[j++] = '-';
for (i = first; i < n && j < outlen - 1; i++) {
int pos = i - first;
if (pos > 0 && (digits - pos) % 3 == 0)
if (j < outlen - 1)
out[j++] = ',';
out[j++] = raw[i];
}
out[j] = '\0';
}
int amount_is_valid(long amount)
{
return (amount >= 1 && amount <= AMOUNT_MAX) ? 1 : 0;
}
'''
UTIL_MSG_C = r'''/*
* util_msg.c - 고정길이 전문 pack/unpack 유틸리티 (plain C, 공유)
*/
#include "acq_util.h"
#include <string.h>
#include <ctype.h>
void msg_field_copy(char *out, const char *field, int len)
{
int end;
if (!out || !field || len < 0)
return;
end = len;
while (end > 0 && (field[end - 1] == ' ' || field[end - 1] == '\0'))
end--;
memcpy(out, field, end);
out[end] = '\0';
}
void msg_field_set(char *field, const char *src, int len)
{
int slen, i;
if (!field || len < 0)
return;
slen = src ? (int)strlen(src) : 0;
if (slen > len)
slen = len;
memcpy(field, src, slen);
for (i = slen; i < len; i++)
field[i] = ' ';
}
void msg_field_set_num(char *field, const char *src, int len)
{
int slen, pad, i;
if (!field || len < 0)
return;
slen = src ? (int)strlen(src) : 0;
if (slen > len)
slen = len;
pad = len - slen;
for (i = 0; i < pad; i++)
field[i] = '0';
if (src)
memcpy(field + pad, src, slen);
}
int msg_unpack(acq_msg_t *msg, const char *raw, int rawlen)
{
if (!msg || !raw)
return -1;
if (rawlen < MSG_ACQ_LEN)
return -1;
memcpy(msg, raw, MSG_ACQ_LEN);
return 0;
}
int msg_pack(const acq_msg_t *msg, char *raw, int rawlen)
{
if (!msg || !raw)
return -1;
if (rawlen < MSG_ACQ_LEN)
return -1;
memcpy(raw, msg, MSG_ACQ_LEN);
return 0;
}
static int field_blank(const char *field, int len)
{
int i;
for (i = 0; i < len; i++)
if (field[i] != ' ' && field[i] != '\0')
return 0;
return 1;
}
int msg_validate(const acq_msg_t *msg)
{
if (!msg)
return -1;
if (field_blank(msg->msg_type, FLD_MSG_TYPE_LEN)) return -1;
if (field_blank(msg->merch_id, FLD_MERCH_ID_LEN)) return -1;
if (field_blank(msg->card_no, FLD_CARD_NO_LEN)) return -1;
if (field_blank(msg->amount, FLD_AMOUNT_LEN)) return -1;
if (field_blank(msg->txn_date, FLD_TXN_DATE_LEN)) return -1;
return 0;
}
'''
# ======================================================================
# 모듈 core 헤더 (표준 DBIO API + 레코드 구조 + 상태코드)
# ======================================================================
CORE_H_TPL = r'''/*
* @mod@_core.h - @KMOD@ 모듈 핵심 계약 (레코드/상태/표준 DBIO API)
*
* 이 모듈의 온라인/배치 프로그램은 SQL 을 직접 다루지 않고 아래 표준
* DBIO API 만 호출한다. 구현은 app/dbio/@mod@_dbio_main.pgc (ECPG).
*/
#ifndef @MOD@_CORE_H
#define @MOD@_CORE_H
#include "txcore.h"
/* @KMOD@ 처리 상태코드 */
#define @MOD@_ST_RECV "R" /* 접수/수신 */
#define @MOD@_ST_DONE "M" /* 처리완료 */
#define @MOD@_ST_FAIL "U" /* 불일치/실패 */
#define @MOD@_ST_SETTLED "S" /* 정산완료 */
/* @KMOD@ 원장 레코드 (1행) */
typedef struct {
char key[16]; /* 처리키(승인/거래 번호) PK */
char merch_id[16]; /* 가맹점번호 */
char card_no[20]; /* 카드번호(마스킹) */
long amount; /* 거래금액(원) */
char biz_date[9]; /* 영업일 YYYYMMDD */
char status[2]; /* 상태코드 */
long fee; /* 수수료(원) */
} @mod@_rec_t;
/* 표준 DBIO API (구현: @mod@_dbio_main.pgc) */
int @mod@_dbio_connect(const char *target);
int @mod@_dbio_disconnect(void);
int @mod@_rec_insert(const @mod@_rec_t *rec);
int @mod@_rec_select(const char *key, @mod@_rec_t *out);
int @mod@_rec_update_status(const char *key, const char *status);
long @mod@_rec_count(const char *biz_date, const char *status);
int @mod@_rec_sum(const char *biz_date, long *out_sum);
#endif /* @MOD@_CORE_H */
'''
DBIO_MAIN_TPL = r'''/* @tier medium @module @mod@ @transform @XFORM@-dbio */
/*
* @mod@_dbio_main.pgc - @KMOD@ 표준 DBIO (ECPG 임베디드 SQL)
*
* @mod@_core.h 의 표준 API 를 @mod@_ledger 테이블에 대해 구현한다.
* 온라인/배치 프로그램은 이 함수들만 호출한다 (self-contained).
*/
#include <stdio.h>
#include <string.h>
#include "txcore.h"
#include "txcore_dbio.h"
#include "@mod@_core.h"
EXEC SQL INCLUDE sqlca;
int @mod@_dbio_connect(const char *target)
{
EXEC SQL BEGIN DECLARE SECTION;
const char *h_target;
EXEC SQL END DECLARE SECTION;
h_target = (target && target[0]) ? target : "acquire@localhost";
EXEC SQL CONNECT TO :h_target;
if (sqlca.sqlcode != TX_SQL_OK) {
TX_DBIO_LOG_ERR("@mod@ CONNECT");
return TX_EDB;
}
tx_log(TX_LOG_INFO, "[@mod@] DB 접속 완료 target=%s", h_target);
return TX_OK;
}
int @mod@_dbio_disconnect(void)
{
EXEC SQL DISCONNECT CURRENT;
return TX_OK;
}
int @mod@_rec_insert(const @mod@_rec_t *rec)
{
EXEC SQL BEGIN DECLARE SECTION;
char h_key[16];
char h_merch_id[16];
char h_card_no[20];
long h_amount;
char h_biz_date[9];
char h_status[2];
long h_fee;
EXEC SQL END DECLARE SECTION;
if (!rec)
return TX_EINVAL;
strncpy(h_key, rec->key, sizeof(h_key) - 1);
h_key[sizeof(h_key) - 1] = '\0';
strncpy(h_merch_id, rec->merch_id, sizeof(h_merch_id) - 1);
h_merch_id[sizeof(h_merch_id) - 1] = '\0';
strncpy(h_card_no, rec->card_no, sizeof(h_card_no) - 1);
h_card_no[sizeof(h_card_no) - 1] = '\0';
strncpy(h_biz_date, rec->biz_date, sizeof(h_biz_date) - 1);
h_biz_date[sizeof(h_biz_date) - 1] = '\0';
strncpy(h_status, rec->status, sizeof(h_status) - 1);
h_status[sizeof(h_status) - 1] = '\0';
h_amount = rec->amount;
h_fee = rec->fee;
EXEC SQL INSERT INTO @mod@_ledger
(key, merch_id, card_no, amount, biz_date, status, fee)
VALUES
(:h_key, :h_merch_id, :h_card_no, :h_amount, :h_biz_date, :h_status, :h_fee);
if (sqlca.sqlcode != TX_SQL_OK) {
TX_DBIO_LOG_ERR("@mod@_rec_insert");
return TX_EDB;
}
return TX_OK;
}
int @mod@_rec_select(const char *key, @mod@_rec_t *out)
{
EXEC SQL BEGIN DECLARE SECTION;
char h_key[16];
char h_merch_id[16];
char h_card_no[20];
long h_amount;
char h_biz_date[9];
char h_status[2];
long h_fee;
EXEC SQL END DECLARE SECTION;
if (!key || !out)
return TX_EINVAL;
strncpy(h_key, key, sizeof(h_key) - 1);
h_key[sizeof(h_key) - 1] = '\0';
EXEC SQL SELECT merch_id, card_no, amount, biz_date, status, fee
INTO :h_merch_id, :h_card_no, :h_amount, :h_biz_date, :h_status, :h_fee
FROM @mod@_ledger
WHERE key = :h_key;
if (sqlca.sqlcode == TX_SQL_NOTFOUND)
return TX_ENOENT;
if (sqlca.sqlcode != TX_SQL_OK) {
TX_DBIO_LOG_ERR("@mod@_rec_select");
return TX_EDB;
}
memset(out, 0, sizeof(*out));
strncpy(out->key, h_key, sizeof(out->key) - 1);
strncpy(out->merch_id, h_merch_id, sizeof(out->merch_id) - 1);
strncpy(out->card_no, h_card_no, sizeof(out->card_no) - 1);
strncpy(out->biz_date, h_biz_date, sizeof(out->biz_date) - 1);
strncpy(out->status, h_status, sizeof(out->status) - 1);
out->amount = h_amount;
out->fee = h_fee;
return TX_OK;
}
int @mod@_rec_update_status(const char *key, const char *status)
{
EXEC SQL BEGIN DECLARE SECTION;
char h_key[16];
char h_status[2];
EXEC SQL END DECLARE SECTION;
if (!key || !status)
return TX_EINVAL;
strncpy(h_key, key, sizeof(h_key) - 1);
h_key[sizeof(h_key) - 1] = '\0';
strncpy(h_status, status, sizeof(h_status) - 1);
h_status[sizeof(h_status) - 1] = '\0';
EXEC SQL UPDATE @mod@_ledger
SET status = :h_status, upd_ts = now()
WHERE key = :h_key;
if (sqlca.sqlcode != TX_SQL_OK) {
TX_DBIO_LOG_ERR("@mod@_rec_update_status");
return TX_EDB;
}
if (sqlca.sqlerrd[2] == 0)
return TX_ENOENT;
return TX_OK;
}
long @mod@_rec_count(const char *biz_date, const char *status)
{
EXEC SQL BEGIN DECLARE SECTION;
char h_biz_date[9];
char h_status[2];
int h_cnt;
EXEC SQL END DECLARE SECTION;
if (!biz_date || !status)
return -1;
strncpy(h_biz_date, biz_date, sizeof(h_biz_date) - 1);
h_biz_date[sizeof(h_biz_date) - 1] = '\0';
strncpy(h_status, status, sizeof(h_status) - 1);
h_status[sizeof(h_status) - 1] = '\0';
EXEC SQL SELECT count(*)
INTO :h_cnt
FROM @mod@_ledger
WHERE biz_date = :h_biz_date
AND status = :h_status;
if (sqlca.sqlcode != TX_SQL_OK) {
TX_DBIO_LOG_ERR("@mod@_rec_count");
return -1;
}
return (long)h_cnt;
}
int @mod@_rec_sum(const char *biz_date, long *out_sum)
{
EXEC SQL BEGIN DECLARE SECTION;
char h_biz_date[9];
long h_sum;
EXEC SQL END DECLARE SECTION;
if (!biz_date || !out_sum)
return TX_EINVAL;
strncpy(h_biz_date, biz_date, sizeof(h_biz_date) - 1);
h_biz_date[sizeof(h_biz_date) - 1] = '\0';
EXEC SQL SELECT coalesce(sum(amount), 0)
INTO :h_sum
FROM @mod@_ledger
WHERE biz_date = :h_biz_date;
if (sqlca.sqlcode != TX_SQL_OK) {
TX_DBIO_LOG_ERR("@mod@_rec_sum");
return TX_EDB;
}
*out_sum = h_sum;
return TX_OK;
}
'''
# ======================================================================
# 온라인 서비스 (.pgc) - easy / medium / hard
# ======================================================================
ONLINE_HEAD = r'''/* @tier @TIER@ @module @mod@ @transform @XFORM@-online */
/*
* @ID@.pgc - @KMOD@ 온라인 서비스 (@SVC@) [tier=@TIER@]
*
* TxCore TX_SERVICE 엔트리. 요청 TXBUF 를 검증하고 @mod@ 표준 DBIO 를
* 통해 처리한 뒤 응답 TXBUF 를 구성한다. (ATMI void SVC(TPSVCINFO*) 대응)
*/
#include <stdio.h>
#include <string.h>
#include "txcore.h"
#include "acq_util.h"
#include "@mod@_core.h"
EXEC SQL INCLUDE sqlca;
'''
ONLINE_EASY = r'''TX_SERVICE(@SVC@, ctx)
{
@mod@_rec_t rec;
char key[16];
int rc;
tx_log(TX_LOG_INFO, "[@SVC@] @KMOD@ 단건조회 서비스 진입");
if (tx_buf_get(ctx->in, "KEY", key, sizeof(key)) != TX_OK) {
tx_log(TX_LOG_ERROR, "[@SVC@] KEY 누락");
tx_return(ctx, TX_EINVAL, ctx->out);
return;
}
memset(&rec, 0, sizeof(rec));
rc = @mod@_rec_select(key, &rec);
if (rc != TX_OK) {
tx_log(TX_LOG_WARN, "[@SVC@] 조회 실패 key=%s rc=%d(%s)",
key, rc, tx_strerror(rc));
tx_return(ctx, rc, ctx->out);
return;
}
tx_buf_reset(ctx->out);
tx_buf_sets(ctx->out, "KEY", rec.key);
tx_buf_sets(ctx->out, "MERCHID", rec.merch_id);
tx_buf_setlong(ctx->out, "AMOUNT", rec.amount);
tx_buf_sets(ctx->out, "STATUS", rec.status);
tx_log(TX_LOG_INFO, "[@SVC@] 조회완료 key=%s amount=%ld", key, rec.amount);
tx_return(ctx, TX_OK, ctx->out);
}
'''
ONLINE_MEDIUM = r'''TX_SERVICE(@SVC@, ctx)
{
@mod@_rec_t rec;
char key[16], merch[16], bizdate[16];
long amount = 0, cnt;
int rc;
tx_log(TX_LOG_INFO, "[@SVC@] @KMOD@ 등록 서비스 진입");
if (tx_buf_get(ctx->in, "KEY", key, sizeof(key)) != TX_OK ||
tx_buf_get(ctx->in, "MERCHID", merch, sizeof(merch)) != TX_OK ||
tx_buf_get(ctx->in, "BIZDATE", bizdate, sizeof(bizdate)) != TX_OK) {
tx_log(TX_LOG_ERROR, "[@SVC@] 필수 필드 누락");
tx_return(ctx, TX_EINVAL, ctx->out);
return;
}
tx_buf_getlong(ctx->in, "AMOUNT", &amount);
/* 업무검증: 금액 범위 + 영업일 형식 */
if (!amount_is_valid(amount)) {
tx_log(TX_LOG_WARN, "[@SVC@] 금액 범위 오류 amount=%ld", amount);
tx_return(ctx, TX_EINVAL, ctx->out);
return;
}
if (!date_is_valid(bizdate)) {
tx_log(TX_LOG_WARN, "[@SVC@] 영업일 오류 date=%s", bizdate);
tx_return(ctx, TX_EINVAL, ctx->out);
return;
}
memset(&rec, 0, sizeof(rec));
strncpy(rec.key, key, sizeof(rec.key) - 1);
strncpy(rec.merch_id, merch, sizeof(rec.merch_id) - 1);
strncpy(rec.biz_date, bizdate, sizeof(rec.biz_date) - 1);
strncpy(rec.status, @MOD@_ST_RECV, sizeof(rec.status) - 1);
rec.amount = amount;
rec.fee = amount / 100; /* 수수료 1% 가정 */
rc = @mod@_rec_insert(&rec);
if (rc != TX_OK) {
tx_log(TX_LOG_ERROR, "[@SVC@] 등록 실패 rc=%d(%s)", rc, tx_strerror(rc));
tx_return(ctx, rc, ctx->out);
return;
}
cnt = @mod@_rec_count(bizdate, @MOD@_ST_RECV);
tx_buf_reset(ctx->out);
tx_buf_sets(ctx->out, "RESPCODE", RESP_OK);
tx_buf_sets(ctx->out, "KEY", key);
tx_buf_setlong(ctx->out, "DAYCNT", (cnt < 0) ? 0 : cnt);
tx_log(TX_LOG_INFO, "[@SVC@] 등록완료 key=%s 당일건수=%ld", key, cnt);
tx_return(ctx, TX_OK, ctx->out);
}
'''
ONLINE_HARD = r'''TX_SERVICE(@SVC@, ctx)
{
@mod@_rec_t rec;
char key[16], merch[16], bizdate[16], nextbiz[9];
long amount = 0, daysum = 0;
int rc;
TXBUF *sub_in;
TXBUF *sub_out;
tx_log(TX_LOG_INFO, "[@SVC@] @KMOD@ 트랜잭션 오케스트레이션 진입");
if (tx_buf_get(ctx->in, "KEY", key, sizeof(key)) != TX_OK ||
tx_buf_get(ctx->in, "MERCHID", merch, sizeof(merch)) != TX_OK ||
tx_buf_get(ctx->in, "BIZDATE", bizdate, sizeof(bizdate)) != TX_OK) {
tx_log(TX_LOG_ERROR, "[@SVC@] 필수 필드 누락");
tx_return(ctx, TX_EINVAL, ctx->out);
return;
}
tx_buf_getlong(ctx->in, "AMOUNT", &amount);
if (!amount_is_valid(amount) || !date_is_valid(bizdate)) {
tx_log(TX_LOG_WARN, "[@SVC@] 입력 검증 실패 amount=%ld date=%s",
amount, bizdate);
tx_return(ctx, TX_EINVAL, ctx->out);
return;
}
/* 트랜잭션 시작 → 원장 반영 → 후속 서비스 연계 → 커밋 */
if (tx_begin() != TX_OK) {
tx_return(ctx, TX_FAIL, ctx->out);
return;
}
memset(&rec, 0, sizeof(rec));
strncpy(rec.key, key, sizeof(rec.key) - 1);
strncpy(rec.merch_id, merch, sizeof(rec.merch_id) - 1);
strncpy(rec.biz_date, bizdate, sizeof(rec.biz_date) - 1);
strncpy(rec.status, @MOD@_ST_RECV, sizeof(rec.status) - 1);
rec.amount = amount;
rec.fee = amount / 100;
rc = @mod@_rec_insert(&rec);
if (rc != TX_OK) {
tx_log(TX_LOG_ERROR, "[@SVC@] 원장반영 실패 rc=%d", rc);
tx_abort();
tx_return(ctx, rc, ctx->out);
return;
}
/* 정산 영업일 = 거래일의 다음 영업일 */
if (date_next_business(bizdate, nextbiz) != 0) {
memcpy(nextbiz, bizdate, 8);
nextbiz[8] = '\0';
}
/* 후속 정산집계 서비스 연계 (레지스트리 문자열 디스패치) */
sub_in = tx_buf_alloc();
sub_out = tx_buf_alloc();
if (sub_in && sub_out) {
tx_buf_sets(sub_in, "MERCHID", merch);
tx_buf_sets(sub_in, "BIZDATE", nextbiz);
tx_buf_setlong(sub_in, "AMOUNT", amount);
rc = tx_call("@NEXTSVC@", sub_in, sub_out);
if (rc != TX_OK)
tx_log(TX_LOG_WARN, "[@SVC@] 후속연계 경고 rc=%d", rc);
}
tx_buf_free(sub_in);
tx_buf_free(sub_out);
/* 교차 검증: 당일 합계 재계산 */
if (@mod@_rec_sum(bizdate, &daysum) != TX_OK)
daysum = 0;
tx_commit();
tx_buf_reset(ctx->out);
tx_buf_sets(ctx->out, "RESPCODE", RESP_OK);
tx_buf_sets(ctx->out, "KEY", key);
tx_buf_setlong(ctx->out, "DAYSUM", daysum);
tx_log(TX_LOG_INFO, "[@SVC@] 완료 key=%s 당일합계=%ld", key, daysum);
tx_return(ctx, TX_OK, ctx->out);
}
'''
# ======================================================================
# 배치 (.pgc) - 커서 DECLARE/OPEN/FETCH 루프 + main()
# ======================================================================
BATCH_TPL = r'''/* @tier @TIER@ @module @mod@ @transform @XFORM@-batch */
/*
* @ID@.pgc - @KMOD@ 배치 (커서 순회 + 상태전이) [tier=@TIER@]
*
* 지정 영업일의 접수(status='R') 건을 커서로 순회하며 업무규칙에 따라
* 완료('M')/불일치('U') 로 상태를 전이한다. 배치 main 엔트리 포함.
*
* 실행: @ID@ <YYYYMMDD> [db@host]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "txcore.h"
#include "txcore_dbio.h"
#include "acq_util.h"
#include "@mod@_core.h"
EXEC SQL INCLUDE sqlca;
static int run_@ID@(const char *biz_date)
{
EXEC SQL BEGIN DECLARE SECTION;
char h_biz_date[9];
char h_key[16];
char h_merch_id[16];
long h_amount;
char h_new_status[2];
EXEC SQL END DECLARE SECTION;
long total = 0, done = 0, unmatch = 0, errcnt = 0;
int rc;
strncpy(h_biz_date, biz_date, sizeof(h_biz_date) - 1);
h_biz_date[sizeof(h_biz_date) - 1] = '\0';
EXEC SQL DECLARE cur_@ID@ CURSOR FOR
SELECT key, merch_id, amount
FROM @mod@_ledger
WHERE biz_date = :h_biz_date
AND status = 'R'
ORDER BY key;
EXEC SQL OPEN cur_@ID@;
if (sqlca.sqlcode != TX_SQL_OK) {
TX_DBIO_LOG_ERR("OPEN cur_@ID@");
return TX_EDB;
}
if (tx_begin() != TX_OK)
tx_log(TX_LOG_WARN, "[@ID@] tx_begin 경고");
for (;;) {
EXEC SQL FETCH cur_@ID@ INTO :h_key, :h_merch_id, :h_amount;
if (sqlca.sqlcode == TX_SQL_NOTFOUND)
break;
if (sqlca.sqlcode != TX_SQL_OK) {
TX_DBIO_LOG_ERR("FETCH cur_@ID@");
errcnt++;
break;
}
total++;
/* 업무규칙: 금액 유효성으로 완료/불일치 판정 */
if (amount_is_valid(h_amount)) {
strncpy(h_new_status, @MOD@_ST_DONE, sizeof(h_new_status));
done++;
} else {
strncpy(h_new_status, @MOD@_ST_FAIL, sizeof(h_new_status));
unmatch++;
}
rc = @mod@_rec_update_status(h_key, h_new_status);
if (rc != TX_OK) {
tx_log(TX_LOG_ERROR, "[@ID@] 상태전이 실패 key=%s rc=%d", h_key, rc);
errcnt++;
}
}
EXEC SQL CLOSE cur_@ID@;
if (errcnt == 0)
tx_commit();
else
tx_abort();
tx_log(TX_LOG_INFO,
"[@ID@] 요약 date=%s 대상=%ld 완료=%ld 불일치=%ld 오류=%ld",
biz_date, total, done, unmatch, errcnt);
printf("========== @KMOD@ 배치 결과 [@ID@] ==========\n");
printf(" 영업일 : %s\n", biz_date);
printf(" 대상 건수 : %ld\n", total);
printf(" 완료(M) : %ld\n", done);
printf(" 불일치(U) : %ld\n", unmatch);
printf(" 오류 건수 : %ld\n", errcnt);
printf("=============================================\n");
return (errcnt == 0) ? TX_OK : TX_FAIL;
}
int main(int argc, char **argv)
{
const char *biz_date;
const char *target;
int rc;
tx_set_loglevel(TX_LOG_INFO);
if (argc < 2) {
fprintf(stderr, "사용법: %s <YYYYMMDD> [db@host]\n", argv[0]);
return 2;
}
biz_date = argv[1];
target = (argc >= 3) ? argv[2] : NULL;
if (!date_is_valid(biz_date)) {
fprintf(stderr, "오류: 유효하지 않은 영업일 '%s'\n", biz_date);
return 2;
}
tx_log(TX_LOG_INFO, "[@ID@] 배치 시작 date=%s", biz_date);
rc = @mod@_dbio_connect(target);
if (rc != TX_OK) {
tx_log(TX_LOG_ERROR, "[@ID@] DB 접속 실패 rc=%d", rc);
return 1;
}
rc = run_@ID@(biz_date);
@mod@_dbio_disconnect();
tx_log(TX_LOG_INFO, "[@ID@] 배치 종료 rc=%d(%s)", rc, tx_strerror(rc));
return (rc == TX_OK) ? 0 : 1;
}
'''
# ======================================================================
# 추가 DBIO (.pgc) - 자체 헤더에 선언된 함수 구현 (self-contained)
# ======================================================================
DBIO_EXTRA_H = r'''/*
* @ID@.h - @KMOD@ 보조 DBIO 선언 (@ID@.pgc 구현)
*/
#ifndef @IDU@_H
#define @IDU@_H
#include "txcore.h"
/* @ID@ 전용 조회/갱신 (테이블 @ID@_t) */
int @ID@_fetch(const char *key, long *out_amount);
int @ID@_touch(const char *key, long amount);
long @ID@_total(const char *biz_date);
#endif /* @IDU@_H */
'''
DBIO_EXTRA_TPL = r'''/* @tier @TIER@ @module @mod@ @transform @XFORM@-dbio */
/*
* @ID@.pgc - @KMOD@ 보조 DBIO (ECPG) [tier=@TIER@]
*/
#include <stdio.h>
#include <string.h>
#include "txcore.h"
#include "txcore_dbio.h"
#include "@ID@.h"
EXEC SQL INCLUDE sqlca;
int @ID@_fetch(const char *key, long *out_amount)
{
EXEC SQL BEGIN DECLARE SECTION;
char h_key[16];
long h_amount;
EXEC SQL END DECLARE SECTION;
if (!key || !out_amount)
return TX_EINVAL;
strncpy(h_key, key, sizeof(h_key) - 1);
h_key[sizeof(h_key) - 1] = '\0';
EXEC SQL SELECT amount
INTO :h_amount
FROM @ID@_t
WHERE key = :h_key;
if (sqlca.sqlcode == TX_SQL_NOTFOUND)
return TX_ENOENT;
if (sqlca.sqlcode != TX_SQL_OK) {
TX_DBIO_LOG_ERR("@ID@_fetch");
return TX_EDB;
}
*out_amount = h_amount;
return TX_OK;
}
int @ID@_touch(const char *key, long amount)
{
EXEC SQL BEGIN DECLARE SECTION;
char h_key[16];
long h_amount;
EXEC SQL END DECLARE SECTION;
if (!key)
return TX_EINVAL;
strncpy(h_key, key, sizeof(h_key) - 1);
h_key[sizeof(h_key) - 1] = '\0';
h_amount = amount;
EXEC SQL UPDATE @ID@_t
SET amount = :h_amount, upd_ts = now()
WHERE key = :h_key;
if (sqlca.sqlcode != TX_SQL_OK) {
TX_DBIO_LOG_ERR("@ID@_touch");
return TX_EDB;
}
if (sqlca.sqlerrd[2] == 0)
return TX_ENOENT;
return TX_OK;
}
long @ID@_total(const char *biz_date)
{
EXEC SQL BEGIN DECLARE SECTION;
char h_biz_date[9];
long h_sum;
EXEC SQL END DECLARE SECTION;
if (!biz_date)
return -1;
strncpy(h_biz_date, biz_date, sizeof(h_biz_date) - 1);
h_biz_date[sizeof(h_biz_date) - 1] = '\0';
EXEC SQL SELECT coalesce(sum(amount), 0)
INTO :h_sum
FROM @ID@_t
WHERE biz_date = :h_biz_date;
if (sqlca.sqlcode != TX_SQL_OK) {
TX_DBIO_LOG_ERR("@ID@_total");
return -1;
}
return h_sum;
}
'''
# ======================================================================
# 공통 유틸 (.c) - plain C
# ======================================================================
COMMON_H = r'''/*
* @ID@.h - @KMOD@ 공통 유틸 선언 (@ID@.c 구현)
*/
#ifndef @IDU@_H
#define @IDU@_H
long @ID@_fee(long amount, int rate_bp);
long @ID@_round_unit(long amount, long unit);
int @ID@_classify(long amount);
#endif /* @IDU@_H */
'''
COMMON_TPL = r'''/* @tier @TIER@ @module @mod@ @transform @XFORM@-util */
/*
* @ID@.c - @KMOD@ 공통 유틸리티 (plain C) [tier=@TIER@]
*/
#include "@ID@.h"
#include <string.h>
/* 수수료 계산: rate_bp 는 basis point(1bp=0.01%) */
long @ID@_fee(long amount, int rate_bp)
{
long fee;
if (amount <= 0 || rate_bp < 0)
return 0;
fee = (amount * (long)rate_bp) / 10000L;
if (fee < 0)
fee = 0;
return fee;
}
/* 절사(내림) 단위 정규화 */
long @ID@_round_unit(long amount, long unit)
{
if (unit <= 0)
return amount;
return (amount / unit) * unit;
}
/* 금액 구간 분류: 0=소액 1=일반 2=고액 */
int @ID@_classify(long amount)
{
if (amount < 10000L)
return 0;
if (amount < 1000000L)
return 1;
return 2;
}
'''
# ======================================================================
# 독립 헤더 (전문 struct / 상수 / 레코드) - 4종 로테이션
# ======================================================================
HDR_REC = r'''/*
* @ID@.h - @KMOD@ 레코드/뷰 구조 정의
*/
#ifndef @IDU@_H
#define @IDU@_H
/* @KMOD@ 처리 뷰 레코드 */
typedef struct {
char key[16];
char merch_id[16];
long amount;
long fee;
char biz_date[9];
char status[2];
char reserved[16];
} @ID@_view_t;
#define @IDU@_STATUS_OPEN "O"
#define @IDU@_STATUS_HOLD "H"
#define @IDU@_STATUS_CLOSE "C"
#endif /* @IDU@_H */
'''
HDR_MSG = r'''/*
* @ID@.h - @KMOD@ 고정길이 전문 레이아웃 (positional, 재배치 금지)
*/
#ifndef @IDU@_H
#define @IDU@_H
typedef struct {
char msg_type[4]; /* 전문구분 4 */
char merch_id[15]; /* 가맹점번호 15 */
char key[9]; /* 처리키 9 */
char amount[12]; /* 금액 12 zero-pad */
char biz_date[8]; /* 영업일 8 */
char resp_code[4]; /* 응답코드 4 */
char filler[20]; /* 예비 20 */
} @ID@_msg_t;
#define @IDU@_MSG_LEN ((int)sizeof(@ID@_msg_t))
#endif /* @IDU@_H */
'''
HDR_CONST = r'''/*
* @ID@.h - @KMOD@ 코드/상수 테이블
*/
#ifndef @IDU@_H
#define @IDU@_H
#define @IDU@_RC_OK 0
#define @IDU@_RC_RETRY 1
#define @IDU@_RC_SKIP 2
#define @IDU@_RC_ERROR 9
#define @IDU@_LIMIT_DAILY 100000000L
#define @IDU@_LIMIT_SINGLE 50000000L
#define @IDU@_FEE_RATE_BP 250 /* 2.50% */
#define @IDU@_CHAN_ONLINE "01"
#define @IDU@_CHAN_BATCH "02"
#define @IDU@_CHAN_RECON "03"
#endif /* @IDU@_H */
'''
HDR_DECL = r'''/*
* @ID@.h - @KMOD@ 내부 헬퍼 선언
*/
#ifndef @IDU@_H
#define @IDU@_H
#include "txcore.h"
int @ID@_check(const char *key, long amount);
int @ID@_apply(TXBUF *in, TXBUF *out);
long @ID@_eval(long amount, int factor);
#endif /* @IDU@_H */
'''
HDR_ROTATION = [HDR_REC, HDR_MSG, HDR_CONST, HDR_DECL]
# ======================================================================
# 데모 서버 main (온라인 데모 링크용, 생성 산출물 검증)
# ======================================================================
DEMO_MAIN_TPL = r'''/*
* demo_@mod@_main.c - @KMOD@ 온라인 데모 부트스트랩 (링크 검증)
*
* 생성된 플래그십 서비스(@SVC@)를 TxCore 레지스트리에 등록하여
* 등록/디스패치/DBIO 링크 경로가 실제로 연결됨을 증명한다.
*/
#include <stdio.h>
#include "txcore.h"
void @SVC@(TXSVCINFO *ctx);
int main(void)
{
tx_set_loglevel(TX_LOG_INFO);
tx_log(TX_LOG_INFO, "@KMOD@ 온라인 데모 기동 (TxCore)");
tx_register("@SVC@", @SVC@);
tx_log(TX_LOG_INFO, "등록 서비스 수 = %d", tx_service_count());
printf("demo_@mod@_server 준비완료: 서비스 %d개 등록\n",
tx_service_count());
return 0;
}
'''
# ======================================================================
# 비컴파일 아티팩트 템플릿
# ======================================================================
FML_TPL = '''#
# @ID@.fml - @KMOD@ FML 필드 테이블 (UBF 필드 정의)
# *base 1@BASE@0
#
# name rel-number type flags comments
@ID@_KEY 1 string - 처리키
@ID@_MERCHID 2 string - 가맹점번호
@ID@_CARDNO 3 string - 카드번호(마스킹)
@ID@_AMOUNT 4 long - 거래금액
@ID@_BIZDATE 5 string - 영업일
@ID@_STATUS 6 char - 상태코드
@ID@_FEE 7 long - 수수료
@ID@_RESPCODE 8 string - 응답코드
'''
SH_TPL = '''#!/bin/sh
# @ID@.sh - @KMOD@ 실행 러너 (@TIER@)
# 사용법: @ID@.sh <YYYYMMDD>
set -eu
BIZDATE="${1:-$(date +%Y%m%d)}"
APP_HOME="${APP_HOME:-/app/acquire-core}"
BIN="${APP_HOME}/bin"
LOG="${APP_HOME}/log/@ID@_${BIZDATE}.log"
echo "[@ID@] @KMOD@ 처리 시작 date=${BIZDATE}"
if [ -x "${BIN}/@RUNBIN@" ]; then
"${BIN}/@RUNBIN@" "${BIZDATE}" "acquire@localhost" >> "${LOG}" 2>&1
RC=$?
else
echo "[@ID@] 실행 파일 없음: ${BIN}/@RUNBIN@" >&2
RC=0
fi
echo "[@ID@] 종료 rc=${RC}"
exit ${RC}
'''
MK_TPL = '''# @ID@.mk - @KMOD@ 빌드 조각 (참고용 include 단편)
# 이 조각은 상위 Makefile 이 아카이브/배포 시 참조하는 메타데이터다.
@IDU@_MODULE := @mod@
@IDU@_TIER := @TIER@
@IDU@_SRC := app/@SUBDIR@/@REF@
@IDU@_DESC := @KMOD@ @XFORM@ 처리 단위
.PHONY: @ID@-info
@ID@-info:
@echo "@ID@ module=@mod@ tier=@TIER@ transform=@XFORM@"
'''
UBB_TPL = '''#
# @ID@.ubb - @KMOD@ TP 도메인 구성 (Tuxedo UBBCONFIG 형식, 참고)
#
*RESOURCES
IPCKEY @IPCKEY@
MASTER SITE1
MODEL SHM
LDBAL Y
*MACHINES
"acqhost" LMID=SITE1
APPDIR="/app/acquire-core"
TUXCONFIG="/app/acquire-core/tuxconfig"
*GROUPS
GRP_@MOD@ LMID=SITE1 GRPNO=@GRPNO@
*SERVERS
@MOD@_SVR SRVGRP=GRP_@MOD@ SRVID=@GRPNO@ CLOPT="-A"
*SERVICES
@MOD@_SVC LOAD=50 PRIO=50
'''
CFG_TPL = '''# @ID@.cfg - @KMOD@ 런타임 설정 (@TIER@)
[general]
module = @mod@
transform = @XFORM@
tier = @TIER@
[db]
dsn = acquire@localhost
schema = public
fetch_size = 1000
[limits]
daily_cap = 100000000
single_cap = 50000000
fee_rate_bp = 250
'''
DAT_TPL = '''# @ID@.dat - @KMOD@ 코드 매핑 데이터 (파이프 구분)
# code|label|value
01|@KMOD@ 접수|R
02|@KMOD@ 완료|M
03|@KMOD@ 불일치|U
04|@KMOD@ 정산|S
'''
SQL_TPL = '''-- @ID@.sql - @KMOD@ DBIO 스키마/뷰 정의 (@TIER@)
-- 런타임 PostgreSQL 에 적용. 컴파일에는 불필요.
CREATE TABLE IF NOT EXISTS @mod@_ledger (
key VARCHAR(15) NOT NULL,
merch_id VARCHAR(15) NOT NULL,
card_no VARCHAR(19) NOT NULL,
amount BIGINT NOT NULL DEFAULT 0,
biz_date CHAR(8) NOT NULL,
status CHAR(1) NOT NULL DEFAULT 'R',
fee BIGINT NOT NULL DEFAULT 0,
reg_ts TIMESTAMP NOT NULL DEFAULT now(),
upd_ts TIMESTAMP,
CONSTRAINT pk_@mod@_ledger PRIMARY KEY (key)
);
CREATE INDEX IF NOT EXISTS ix_@ID@_date_status
ON @mod@_ledger (biz_date, status);
CREATE OR REPLACE VIEW @ID@_daily_v AS
SELECT biz_date, status, count(*) AS cnt, sum(amount) AS total
FROM @mod@_ledger
GROUP BY biz_date, status;
'''
SCHEMA_SQL = '''-- schema.sql - acquire-core-full 공통 스키마 루트
-- 모듈별 원장 테이블은 db/<mod>_*.sql 참조.
CREATE TABLE IF NOT EXISTS merchant (
merch_id VARCHAR(15) NOT NULL,
merch_name VARCHAR(60) NOT NULL,
biz_no VARCHAR(12),
status CHAR(1) NOT NULL DEFAULT 'A',
CONSTRAINT pk_merchant PRIMARY KEY (merch_id)
);
CREATE TABLE IF NOT EXISTS approval (
appr_no VARCHAR(9) NOT NULL,
merch_id VARCHAR(15) NOT NULL,
amount BIGINT NOT NULL,
appr_date CHAR(8) NOT NULL,
status CHAR(1) NOT NULL DEFAULT 'A',
CONSTRAINT pk_approval PRIMARY KEY (appr_no)
);
'''
# ======================================================================
# 생성 로직
# ======================================================================
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--fraction", type=float, default=1.0,
help="목표 건수 대비 생성 비율 (테스트용)")
args = ap.parse_args()
frac = args.fraction
def scaled(name, floor):
return max(floor, int(round(TARGETS[name] * frac)))
n_online = scaled("online", 11)
n_batch = scaled("batch", 11)
n_common = scaled("common", 11)
n_dbio = scaled("dbio", 22) # 11 main + extras
n_dbsql = scaled("dbio_sql", 11)
n_fml = scaled("fml", 11)
n_sh = scaled("sh", 11)
n_mk = scaled("mk", 11)
n_config = scaled("config", 11)
n_mods = len(MOD_CODES)
n_dbio_extra = max(0, n_dbio - n_mods) # main 은 모듈당 1개
# 헤더: core(11) + io헤더(extra dbio 수) + cu헤더(common 수) + standalone
n_hdr_target = scaled("headers", n_mods + 4)
n_standalone = max(0, n_hdr_target - n_mods - n_dbio_extra - n_common)
# ---- 디렉토리 초기화 -------------------------------------------------
for sub in ["framework", "app", "fml", "scripts", "mk", "config", "db",
"build", "bin"]:
p = os.path.join(ROOT, sub)
if os.path.isdir(p):
shutil.rmtree(p)
for f in ["Makefile", "inventory.json"]:
p = os.path.join(ROOT, f)
if os.path.isfile(p):
os.remove(p)
def wr(relpath, content):
p = os.path.join(ROOT, relpath)
os.makedirs(os.path.dirname(p), exist_ok=True)
with open(p, "w", encoding="utf-8") as fh:
fh.write(content)
inventory = []
counts = {}
def bump(k, n=1):
counts[k] = counts.get(k, 0) + n
def loc_of(s):
return s.count("\n") + (0 if s.endswith("\n") else 1)
def sqlcount(s):
return s.count("EXEC SQL")
def add_inv(pid, module, archetype, tier, path, content):
inventory.append({
"id": pid,
"module": module,
"archetype": archetype,
"tier": tier,
"path": path,
"loc": loc_of(content),
"sqlCount": sqlcount(content),
})
# ---- 프레임워크 (공유) ----------------------------------------------
wr("framework/txcore/include/txcore.h", TXCORE_H)
wr("framework/txcore/dbio/txcore_dbio.h", TXCORE_DBIO_H)
wr("framework/txcore/src/txcore.c", TXCORE_C)
wr("app/shared/include/msg_layout.h", MSG_LAYOUT_H)
wr("app/shared/include/acq_util.h", ACQ_UTIL_H)
wr("app/shared/common/util_date.c", UTIL_DATE_C)
wr("app/shared/common/util_amount.c", UTIL_AMOUNT_C)
wr("app/shared/common/util_msg.c", UTIL_MSG_C)
bump("framework", 3)
bump("shared_hdr", 2)
bump("shared_c", 3)
# ---- 모듈 core 헤더 + dbio_main -------------------------------------
first_online = {} # mod -> flagship online id
first_batch = {} # mod -> flagship batch id
for mod, kname, xform in MODULES:
core = render(CORE_H_TPL, mod=mod, MOD=mod.upper(),
KMOD=kname, XFORM=xform)
wr("app/include/%s_core.h" % mod, core)
bump("headers")
dm = render(DBIO_MAIN_TPL, mod=mod, MOD=mod.upper(),
KMOD=kname, XFORM=xform)
pid = "%s_dbio_main" % mod
path = "app/dbio/%s.pgc" % pid
wr(path, dm)
add_inv(pid, mod, "dbio", "medium", path, dm)
bump("dbio")
# ---- 온라인 서비스 ---------------------------------------------------
seq = {m: 0 for m in MOD_CODES}
for i in range(n_online):
mod = MOD_CODES[i % n_mods]
seq[mod] += 1
pid = "%s_ol_%04d" % (mod, seq[mod])
svc = pid.upper()
tier = tier_for(i)
kname = MOD_KNAME[mod]
xform = MOD_XFORM[mod]
if mod not in first_online:
first_online[mod] = (pid, svc)
body = {"easy": ONLINE_EASY, "medium": ONLINE_MEDIUM,
"hard": ONLINE_HARD}[tier]
content = render(ONLINE_HEAD + body, mod=mod, MOD=mod.upper(),
ID=pid, SVC=svc, TIER=tier, KMOD=kname, XFORM=xform,
NEXTSVC="%s_POST" % mod.upper())
path = "app/online/%s.pgc" % pid
wr(path, content)
add_inv(pid, mod, "online", tier, path, content)
bump("online")
# ---- 배치 ------------------------------------------------------------
bseq = {m: 0 for m in MOD_CODES}
for i in range(n_batch):
mod = MOD_CODES[i % n_mods]
bseq[mod] += 1
pid = "%s_bt_%04d" % (mod, bseq[mod])
tier = tier_for(i)
kname = MOD_KNAME[mod]
xform = MOD_XFORM[mod]
if mod not in first_batch:
first_batch[mod] = pid
content = render(BATCH_TPL, mod=mod, MOD=mod.upper(), ID=pid,
TIER=tier, KMOD=kname, XFORM=xform)
path = "app/batch/%s.pgc" % pid
wr(path, content)
add_inv(pid, mod, "batch", tier, path, content)
bump("batch")
# ---- 추가 DBIO -------------------------------------------------------
ioseq = {m: 0 for m in MOD_CODES}
for i in range(n_dbio_extra):
mod = MOD_CODES[i % n_mods]
ioseq[mod] += 1
pid = "%s_io_%04d" % (mod, ioseq[mod])
tier = tier_for(i)
kname = MOD_KNAME[mod]
xform = MOD_XFORM[mod]
hdr = render(DBIO_EXTRA_H, ID=pid, IDU=pid.upper(), KMOD=kname)
wr("app/include/%s.h" % pid, hdr)
bump("headers")
content = render(DBIO_EXTRA_TPL, mod=mod, ID=pid, TIER=tier,
KMOD=kname, XFORM=xform)
path = "app/dbio/%s.pgc" % pid
wr(path, content)
add_inv(pid, mod, "dbio", tier, path, content)
bump("dbio")
# ---- 공통 유틸 -------------------------------------------------------
cseq = {m: 0 for m in MOD_CODES}
for i in range(n_common):
mod = MOD_CODES[i % n_mods]
cseq[mod] += 1
pid = "%s_cu_%04d" % (mod, cseq[mod])
tier = tier_for(i)
kname = MOD_KNAME[mod]
xform = MOD_XFORM[mod]
hdr = render(COMMON_H, ID=pid, IDU=pid.upper(), KMOD=kname)
wr("app/include/%s.h" % pid, hdr)
bump("headers")
content = render(COMMON_TPL, mod=mod, ID=pid, TIER=tier,
KMOD=kname, XFORM=xform)
path = "app/common/%s.c" % pid
wr(path, content)
add_inv(pid, mod, "common", tier, path, content)
bump("common")
# ---- 독립 헤더 -------------------------------------------------------
hseq = {m: 0 for m in MOD_CODES}
for i in range(n_standalone):
mod = MOD_CODES[i % n_mods]
hseq[mod] += 1
pid = "%s_h_%04d" % (mod, hseq[mod])
kname = MOD_KNAME[mod]
tpl = HDR_ROTATION[i % len(HDR_ROTATION)]
content = render(tpl, ID=pid, IDU=pid.upper(), KMOD=kname)
wr("app/include/%s.h" % pid, content)
bump("headers")
# ---- 데모 서버 main (온라인 데모) -----------------------------------
for mod in ONLINE_DEMOS:
if mod not in first_online:
continue
_pid, svc = first_online[mod]
content = render(DEMO_MAIN_TPL, mod=mod, SVC=svc, KMOD=MOD_KNAME[mod])
wr("app/common/demo_%s_main.c" % mod, content)
bump("demo_main")
# ---- FML -------------------------------------------------------------
for i in range(n_fml):
mod = MOD_CODES[i % n_mods]
pid = "%s_fml_%04d" % (mod, i // n_mods + 1)
content = render(FML_TPL, ID=pid.upper(), KMOD=MOD_KNAME[mod],
BASE=(i % 9) + 1)
wr("fml/%s.fml" % pid, content)
bump("fml")
# ---- 셸 러너 ---------------------------------------------------------
for i in range(n_sh):
mod = MOD_CODES[i % n_mods]
pid = "%s_run_%04d" % (mod, i // n_mods + 1)
tier = tier_for(i)
runbin = "demo_%s_batch" % mod if mod in BATCH_DEMOS else \
first_batch.get(mod, "%s_bt_0001" % mod)
content = render(SH_TPL, ID=pid, TIER=tier, KMOD=MOD_KNAME[mod],
RUNBIN=runbin)
wr("scripts/%s.sh" % pid, content)
os.chmod(os.path.join(ROOT, "scripts/%s.sh" % pid), 0o755)
bump("sh")
# ---- Makefile 조각 ---------------------------------------------------
for i in range(n_mk):
mod = MOD_CODES[i % n_mods]
pid = "%s_frag_%04d" % (mod, i // n_mods + 1)
tier = tier_for(i)
content = render(MK_TPL, ID=pid, IDU=pid.upper(), mod=mod, TIER=tier,
KMOD=MOD_KNAME[mod], XFORM=MOD_XFORM[mod],
SUBDIR="online", REF="%s_ol_0001.pgc" % mod)
wr("mk/%s.mk" % pid, content)
bump("mk")
# ---- 설정 (.ubb/.cfg/.dat) ------------------------------------------
cfg_exts = ["ubb", "cfg", "dat"]
for i in range(n_config):
mod = MOD_CODES[i % n_mods]
ext = cfg_exts[i % 3]
pid = "%s_cfg_%04d" % (mod, i // n_mods + 1)
if ext == "ubb":
content = render(UBB_TPL, ID=pid, MOD=mod.upper(),
KMOD=MOD_KNAME[mod],
IPCKEY=str(70000 + i), GRPNO=str((i % 30) + 1))
elif ext == "cfg":
content = render(CFG_TPL, ID=pid, mod=mod,
XFORM=MOD_XFORM[mod], TIER=tier_for(i))
else:
content = render(DAT_TPL, ID=pid, KMOD=MOD_KNAME[mod])
wr("config/%s.%s" % (pid, ext), content)
bump("config")
# ---- DBIO SQL 스키마 -------------------------------------------------
wr("db/schema.sql", SCHEMA_SQL)
for i in range(n_dbsql):
mod = MOD_CODES[i % n_mods]
pid = "%s_ddl_%04d" % (mod, i // n_mods + 1)
content = render(SQL_TPL, ID=pid, mod=mod, KMOD=MOD_KNAME[mod],
TIER=tier_for(i))
wr("db/%s.sql" % pid, content)
bump("dbio_sql")
# ---- Makefile --------------------------------------------------------
wr("Makefile", build_makefile(first_online, first_batch))
# ---- inventory.json --------------------------------------------------
tier_hist = {}
arche_hist = {}
for r in inventory:
tier_hist[r["tier"]] = tier_hist.get(r["tier"], 0) + 1
arche_hist[r["archetype"]] = arche_hist.get(r["archetype"], 0) + 1
inv_doc = {
"corpus": "acquire-core-full",
"domain": "card acquiring / settlement (카드 매입·정산)",
"framework": "TxCore (ECPG + 고정길이 전문 + TP 관용구)",
"generatedBy": "generate.py",
"fraction": frac,
"fileCounts": counts,
"programCount": len(inventory),
"byTier": tier_hist,
"byArchetype": arche_hist,
"modules": {m[0]: m[1] for m in MODULES},
"programs": sorted(inventory, key=lambda r: r["path"]),
}
with open(os.path.join(ROOT, "inventory.json"), "w", encoding="utf-8") as fh:
json.dump(inv_doc, fh, ensure_ascii=False, indent=2)
# ---- 요약 출력 -------------------------------------------------------
total_files = 0
for _root, _dirs, files in os.walk(ROOT):
if "/.git" in _root:
continue
for f in files:
total_files += 1
print("=== acquire-core-full 생성 완료 (fraction=%.3f) ===" % frac)
for k in sorted(counts):
print(" %-12s %5d" % (k, counts[k]))
print(" programs(inv) %5d" % len(inventory))
print(" tier ", tier_hist)
print(" 총 파일수 ", total_files)
def build_makefile(first_online, first_batch):
lines = []
A = lines.append
A("# ==========================================================================")
A("# acquire-core-full / TxCore Makefile (생성기 산출물)")
A("#")
A("# .pgc --ecpg--> .c --gcc--> build/*.o --ar--> libtxcore.a --ld--> bin/*")
A("#")
A("# ~1000개 .c/.pgc 를 build/*.o 로 컴파일하는 것이 빌드 검증의 핵심.")
A("# 와일드카드로 전체 소스를 수집하고, 대표 데모 바이너리 몇 개를 링크한다.")
A("# ==========================================================================")
A("CC := gcc")
A("ECPG := ecpg")
A("AR := ar")
A("PG_INCDIR := $(shell pg_config --includedir 2>/dev/null)")
A("")
A("INCLUDES := -Iframework/txcore/include \\")
A(" -Iframework/txcore/dbio \\")
A(" -Iapp/include \\")
A(" -Iapp/shared/include \\")
A(" -I$(PG_INCDIR)")
A("")
A("# 고정길이 관용구 경고만 억제 (-Werror 없음)")
A("CFLAGS := -g -O2 -Wall -Wextra -Wno-unused-parameter \\")
A(" -Wno-stringop-truncation -Wno-format-truncation \\")
A(" -Wno-unused-variable -Wno-unused-but-set-variable $(INCLUDES)")
A("ECPGFLAGS := -Iframework/txcore/include -Iframework/txcore/dbio \\")
A(" -Iapp/include -Iapp/shared/include")
A("LDLIBS := -lecpg -lpq")
A("")
A("BUILD := build")
A("BIN := bin")
A("")
A("# ---- 소스 수집 (와일드카드) ----------------------------------------------")
A("ONLINE_PGC := $(wildcard app/online/*.pgc)")
A("BATCH_PGC := $(wildcard app/batch/*.pgc)")
A("DBIO_PGC := $(wildcard app/dbio/*.pgc)")
A("COMMON_C := $(wildcard app/common/*.c)")
A("SHARED_C := $(wildcard app/shared/common/*.c)")
A("")
A("PGC_ALL := $(ONLINE_PGC) $(BATCH_PGC) $(DBIO_PGC)")
A("GEN_C := $(PGC_ALL:.pgc=.c)")
A("")
A("PGC_OBJ := $(patsubst %,$(BUILD)/%.o,$(basename $(notdir $(PGC_ALL))))")
A("COMMON_OBJ := $(patsubst %,$(BUILD)/%.o,$(basename $(notdir $(COMMON_C))))")
A("SHARED_OBJ := $(patsubst %,$(BUILD)/%.o,$(basename $(notdir $(SHARED_C))))")
A("ALL_OBJ := $(PGC_OBJ) $(COMMON_OBJ) $(SHARED_OBJ)")
A("")
A("LIBTXCORE := $(BUILD)/libtxcore.a")
A("")
# 데모 바이너리 목록
demo_bins = []
for mod in ONLINE_DEMOS:
if mod in first_online:
demo_bins.append("$(BIN)/demo_%s_server" % mod)
for mod in BATCH_DEMOS:
if mod in first_batch:
demo_bins.append("$(BIN)/demo_%s_batch" % mod)
A("DEMO_BINS := " + " ".join(demo_bins))
A("")
A(".SECONDARY:")
A(".PHONY: all clean dirs gen objs demos")
A("")
A("all: dirs $(LIBTXCORE) objs demos")
A("\t@echo \"==> 빌드 완료\"")
A("\t@echo \"오브젝트: $(words $(ALL_OBJ)) 개\"")
A("")
A("dirs:")
A("\t@mkdir -p $(BUILD) $(BIN)")
A("")
A("gen: $(GEN_C)")
A("objs: $(ALL_OBJ)")
A("demos: $(DEMO_BINS)")
A("")
A("# ---- ecpg: .pgc -> .c (디렉토리별) ---------------------------------------")
A("app/online/%.c: app/online/%.pgc")
A("\t$(ECPG) $(ECPGFLAGS) -o $@ $<")
A("app/batch/%.c: app/batch/%.pgc")
A("\t$(ECPG) $(ECPGFLAGS) -o $@ $<")
A("app/dbio/%.c: app/dbio/%.pgc")
A("\t$(ECPG) $(ECPGFLAGS) -o $@ $<")
A("")
A("# ---- 컴파일: build/<basename>.o (gen 선행 보장) --------------------------")
A("$(BUILD)/%.o: app/online/%.c | gen")
A("\t$(CC) $(CFLAGS) -c $< -o $@")
A("$(BUILD)/%.o: app/batch/%.c | gen")
A("\t$(CC) $(CFLAGS) -c $< -o $@")
A("$(BUILD)/%.o: app/dbio/%.c | gen")
A("\t$(CC) $(CFLAGS) -c $< -o $@")
A("$(BUILD)/%.o: app/common/%.c")
A("\t$(CC) $(CFLAGS) -c $< -o $@")
A("$(BUILD)/%.o: app/shared/common/%.c")
A("\t$(CC) $(CFLAGS) -c $< -o $@")
A("")
A("# ---- TxCore 정적 라이브러리 ----------------------------------------------")
A("$(BUILD)/txcore.o: framework/txcore/src/txcore.c")
A("\t$(CC) $(CFLAGS) -c $< -o $@")
A("$(LIBTXCORE): $(BUILD)/txcore.o")
A("\t$(AR) rcs $@ $^")
A("")
A("# ---- 대표 데모 바이너리 (링크 검증) --------------------------------------")
for mod in ONLINE_DEMOS:
if mod not in first_online:
continue
flag_id, _svc = first_online[mod]
objs = "$(BUILD)/demo_%s_main.o $(BUILD)/%s.o $(BUILD)/%s_dbio_main.o $(SHARED_OBJ)" % (
mod, flag_id, mod)
A("$(BIN)/demo_%s_server: %s $(LIBTXCORE)" % (mod, objs))
A("\t$(CC) $(CFLAGS) -o $@ %s -L$(BUILD) -ltxcore $(LDLIBS)" % objs)
A("")
for mod in BATCH_DEMOS:
if mod not in first_batch:
continue
bid = first_batch[mod]
objs = "$(BUILD)/%s.o $(BUILD)/%s_dbio_main.o $(SHARED_OBJ)" % (bid, mod)
A("$(BIN)/demo_%s_batch: %s $(LIBTXCORE)" % (mod, objs))
A("\t$(CC) $(CFLAGS) -o $@ %s -L$(BUILD) -ltxcore $(LDLIBS)" % objs)
A("")
A("# ==========================================================================")
A("clean:")
A("\trm -rf $(BUILD) $(BIN)")
A("\trm -f $(GEN_C)")
A("")
return "\n".join(lines) + "\n"
if __name__ == "__main__":
main()