MeteorCat / Rebar3构建脚本

Created Sat, 17 Aug 2024 02:17:15 +0800 Modified Wed, 29 Oct 2025 23:24:53 +0800
4446 Words

用于自定义的初始化打包 Rebar3 项目:

# 构建脚本
vim GenRebar3Project.py

脚本内容:

# !/usr/bin/python
# -*- coding: UTF-8 -*-
# ===================================================
# 构建 Rebar3 项目工程
# ===================================================

import argparse
import os
import re
import shutil
import time
import urllib.request
import subprocess
from datetime import datetime

ROOT_DIR = os.path.dirname(os.path.realpath(__file__))
REBAR_CDN = "https://s3.amazonaws.com/rebar3/rebar3"
REBAR_EXE = "rebar3"
REBAR_DOC = "https://rebar3.org/docs/getting-started"
ERL_SCRIPT = "escript.exe" if os.name == "nt" else "escript"
ERL_EXE = "werl.exe" if os.name == "nt" else "erl"


# 重写配置文件
def rewrite_rebar_config(project_dir, project_name):
    rebar_config = os.path.join(project_dir, "rebar.config")
    if not os.path.exists(rebar_config):
        raise Exception("Config Non Exists")

    ymd = datetime.now().strftime("%Y.%m.%d")

    # 改写模板
    template = r"""
{minimum_otp_vsn, "24.0"}. % OTP最低版本
{root_dir, "."}. % 根目录
{base_dir, "_build"}. % 编译目录
{deps_dir, "lib"}. % 第三方库目录

%% 启动配置
{erl_opts, [
    {platform_define, "^([1-9][0-9][0-9].*?)|([2-9][0-9].*?)|(1[8-9])", namespaced_types}, % emysql 依赖
    %% LAGER IMPORT HEADER --- {parse_transform, lager_transform}, % lager 必须
    {i, "include"}, % 应用头文件
    
    % 系统其他配置
    warn_keywords,
    debug_info
]}.

%% 发布编译配置
{relx, [{release, {__PROJECT__, "__VERSION__"},
    [
        sasl,
        crypto,
        utils,
        __PROJECT__
    ]},
    
    {mode, dev},
    
    %% automatically picked up if the files
    %% exist but can be set manually, which
    %% is required if the names aren't exactly
    %% sys.config and vm.args
    {sys_config, "./config/sys.config"},
    {vm_args, "./config/vm.args"},
    
    %% the .src form of the configuration files do
    %% not require setting RELX_REPLACE_OS_VARS
    %% {sys_config_src, "./config/sys.config.src"},
    %% {vm_args_src, "./config/vm.args.src"}
    
    {extended_start_script, true}
]}.

%% @doc 打包设置
{profiles, [

    %% 测试包
    %% 打包: rebar3 as dev release
    %% 压缩包: rebar3 as dev tar
    {prod, [{relx, [
        {dev_mode, true},
        {include_erts, false},
        {include_src, true},
        {debug_info, true}
    ]}]},
    
    %% 正式包
    {prod, [{relx, [
        {dev_mode, false},
        {include_erts, true},
        {include_src, false},
        {debug_info, strip}]}
    ]}
]}.

%% 命令行调用
%% 命令行调用 VM 需要配置环境变量: ERL_FLAGS = -args_file config/vm.args
{shell, [
  {config, "config/sys.config"},
  {apps, [
    sasl,
    crypto,
    utils,
    __PROJECT__ 
  ]}
]}.


%% CDN
{rebar_packages_cdn, "https://hexpm.upyun.com"}.
    """.replace("__VERSION__", ymd).replace("__PROJECT__", project_name)
    with open(rebar_config, "w", encoding="utf-8") as f:
        f.write(template)


# 重写应用配置
def rewrite_app_config(project_dir, project_name):
    sys_config = os.path.join(project_dir, "config", "sys.config")
    vm_args = os.path.join(project_dir, "config", "vm.args")
    if not os.path.exists(sys_config) or not os.path.exists(vm_args):
        raise Exception("AppConfig Non Exists")

    # 写入 sys.config
    template = r"""
[
    {__PROJECT___app, []}
].
""".replace("__PROJECT__", project_name)
    with open(sys_config, "w", encoding="utf-8") as f:
        f.write(template)

    # 写入 vm.args
    template = r"""
-sname __PROJECT___1 # 启动分布式, 多服就按照 __PROJECT___1,__PROJECT___2,... 分服, node() 查看节点
-setcookie __PROJECT___cookie # 节点会话标识
-smp enable # 支持SMP调度

+K true # 启用异步池
+pc unicode # 启用多语言支持
+P 102400 # 最大进程数
+A 30 # 异步池数量
+e 102400 # ETS最大数量
    """.replace("__PROJECT__", project_name)
    with open(vm_args, "w", encoding="utf-8") as f:
        f.write(template)


# 追加全局头文件
def rewrite_constant_header(project_dir, project_name):
    project_keyboard = str.upper(project_name)
    project_header_dir = os.path.join(project_dir, "include")
    project_header_file = os.path.join(project_header_dir, "constant.hrl")
    if not os.path.exists(project_header_dir):
        os.mkdir(project_header_dir)

    # 写入 constant.hrl
    ymd = datetime.now().strftime("%Y%m%d")
    template = r"""
%%%-------------------------------------------------------------------
%%% @author MeteorCat
%%% @copyright (C) 2024, MeteorCat
%%% @doc 
%%% 全局常量
%%% @end
%%%-------------------------------------------------------------------
-author("MeteorCat").

-ifndef(__CONSTANT_NAME__).
-define(__CONSTANT_NAME__, __CONSTANT_VER__).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%  系统原子量定义 - Begin
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-define(true, true).
-define(false, false).
-define(null, null).
-define(ok, ok).
-define(error, error).
-define(exit, exit).
-define(exec, exec).
-define(done, done).
-define(stop, stop).
-define(skip, skip).
-define(continue, continue).
-define(normal, normal).
-define(takeover, takeover).
-define(failover, failover).
-define(undefined, undefined).
-define(ignore, ignore).
-define(trap_exit, trap_exit).
-define(local, local).
-define(hibernate, hibernate).
-define(reply, reply).
-define(noreply, noreply).
-define(shutdown, shutdown).
-define(inet_reply, inet_reply).
-define(loop, loop).
-define(unknown, unknown).
-define(unsupported, unsupported).
-define(register, register).
-define(unregister, unregister).
-define(closed, closed).
-define(badarg, badarg).
-define(timeout, timeout).
-define(interval, interval).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%  系统原子量定义 - End
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%  定义类型, 参考 Rust 的定义 - Begin
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-define(nan, nan). %% 不是数值类型
-define(bool, boolean). %% 布尔值
-define(i8, i8). %% 8位带符号整型
-define(u8, u8). %% 8位无符号整型
-define(i16, i16). %% 16位带符号整型
-define(u16, u16). %% 16位无符号整型
-define(i32, i32). %% 32位带符号整型
-define(u32, u32). %% 32位无符号整型
-define(i64, i64). %% 64位带符号整型
-define(u64, u64). %% 64位无符号整型
-define(f64, f64). %% 双精度(64 位)浮点数
-define(str, str). %% 字符串

% 直接类型声明
-define(u8_t, 8 / big - integer - unsigned).
-define(u16_t, 16 / big - integer - unsigned).
-define(u32_t, 32 / big - integer - unsigned).
-define(u64_t, 64 / big - integer - unsigned).
-define(i8_t, 8 / big - integer - signed).
-define(i16_t, 16 / big - integer - signed).
-define(i32_t, 32 / big - integer - signed).
-define(i64_t, 64 / big - integer - signed).
-define(f32_t, 32 / big - float).
-define(f64_t, 64 / big - float).
-define(bytes_t, bytes).


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%  定义类型 - End
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%



%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%  宏函数 - Begin
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-define(IF(C, L, R), case (C) of ?true -> (L);?false -> (R) end).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%  宏函数 - End
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


-endif.  
    """.replace("__CONSTANT_NAME__", "__CONSTANT_" + project_keyboard + "__").replace("__CONSTANT_VER__", ymd)
    with open(project_header_file, "w", encoding="utf-8") as f:
        f.write(template)


# 追加转化文件
def rewrite_convert_erl(project_dir, project_name):
    # 不存在目录直接创建
    project_apps_dir = os.path.join(project_dir, "apps")
    project_tools_dir = os.path.join(project_apps_dir, "utils", "src")
    if not os.path.isdir(project_tools_dir):
        os.makedirs(project_tools_dir)

    # convert_utils.erl
    template = r"""
%%%-------------------------------------------------------------------
%%% @author MeteorCat
%%% @copyright (C) 2024, MeteorCat
%%% @doc 
%%% 转化方法
%%% @end
%%%-------------------------------------------------------------------
-module(convert_utils).
-author("MeteorCat").
-include("constant.hrl").


%% API
-export([
  list2atom/1,
  to_list/1,
  list2string/4,
  string2term/1,
  term2bitstring/1,
  bitstring2term/1,
  term2string/1,
  to_atom/1,
  to_binary/1,
  to_float/1,
  to_integer/1,
  to_tuple/1,
  term2binary/1,
  binary2term/1,
  binary2hex/1,
  list2hex/1
]).

%% @doc List转换Atom
-spec list2atom(list()) -> term().
list2atom(L) ->
  try erlang:list_to_existing_atom(L)
  catch _:_ -> erlang:list_to_atom(L) end.
  

%% @doc list 转换方法
to_list(V) when erlang:is_list(V) -> V;
to_list(V) when erlang:is_tuple(V) ->
  erlang:tuple_to_list(V);
to_list(V) when erlang:is_atom(V) ->
  erlang:atom_to_list(V);
to_list(V) when erlang:is_binary(V) ->
  erlang:binary_to_list(V);
to_list(V) when erlang:is_integer(V) ->
  erlang:integer_to_list(V);
to_list(V) when erlang:is_float(V) ->
  erlang:float_to_list(V);
to_list(_) -> [].


%% @doc 数组转字符串
%% H 附加在开头
%% M 夹在中间
%% T 附加在尾部
list2string([], _, _, _) -> [];
list2string([Head | Tail], H, M, T) ->
  list2string(Tail, H, M, T, Head ++ to_list(Head)).
list2string([], _, _, T, Str) -> Str ++ T;
list2string([Head | Tail], H, M, T, Str) ->
  list2string(Tail, H, M, T, Str ++ M ++ to_list(Head)).


%%% @doc term序列化, term转换为string格式
%%% [{a},1] => "[{a},1]"
term2string(Term) -> binary_to_list(term2bitstring(Term)).
string2term(Str) ->
  case erl_scan:string(Str ++ ".") of
    {?ok, Token, _} ->
      case erl_parse:parse_term(Token) of
        {?ok, Term} -> Term;
        _ -> ?null
      end;
    _ -> ?null
  end.
  
  
%%% @doc Term 序列化, term转换为bitstring格式
%%% [{a},1] => <<"[{a},1]">>
term2bitstring(Term) -> erlang:iolist_to_binary(io_lib:write(Term)).
bitstring2term(?undefined) -> ?undefined;
bitstring2term(BitStr) -> string2term(erlang:binary_to_list(BitStr)).

%%% @doc atom 转换方法
to_atom(V) when erlang:is_atom(V) -> V;
to_atom(V) when erlang:is_binary(V) ->
  list2atom(erlang:binary_to_list(V));
to_atom(V) when erlang:is_integer(V) ->
  list2atom(erlang:integer_to_list(V));
to_atom(V) when erlang:is_float(V) ->
  list2atom(erlang:float_to_list(V));
to_atom(V) when erlang:is_tuple(V) ->
  list2atom(erlang:tuple_to_list(V));
to_atom(V) when erlang:is_list(V) ->
  V2 = erlang:list_to_binary(V),
  V3 = erlang:binary_to_list(V2),
  list2atom(V3);
to_atom(_) -> list2atom("").


%%%  @doc binary 转换方法
to_binary(V) when erlang:is_binary(V) -> V;
to_binary(V) when erlang:is_atom(V) ->
  erlang:list_to_binary(erlang:atom_to_list(V));
to_binary(V) when erlang:is_list(V) ->
  erlang:list_to_binary(V);
to_binary(V) when erlang:is_integer(V) ->
  erlang:list_to_binary(integer_to_list(V));
to_binary(V) when erlang:is_float(V) ->
  erlang:list_to_binary(float_to_list(V));
to_binary(_) -> <<>>.


%%% float 转化方法
to_float(V) -> list_to_float(?MODULE:to_list(V)).


%%% integer 转化方法
to_integer(V) when erlang:is_integer(V) -> V;
to_integer(V) when erlang:is_binary(V) ->
  V2 = erlang:binary_to_list(V),
  erlang:list_to_integer(V2);
to_integer(V) when erlang:is_list(V) ->
  erlang:list_to_integer(V);
to_integer(_) -> 0.


%% @doc tuple 转化方法
to_tuple(T) when erlang:is_tuple(T) -> T;
to_tuple(T) when erlang:is_list(T) ->
  erlang:list_to_tuple(T);
to_tuple(T) -> {T}.


%% @doc 结构体序列化
-spec term2binary(term()) -> erlang:ext_binary().
term2binary(Term) -> erlang:term_to_binary(Term).

%% @doc 结构体反序列化
-spec binary2term(erlang:ext_binary()) -> term().
binary2term(Bytes) -> erlang:binary_to_term(Bytes).

%% 二进制转16进制字符
-spec binary2hex(bitstring()) -> list().
binary2hex(Bin) ->
  List = binary_to_list(Bin),
  lists:flatten(list2hex(List)).

%% 列表转16进制字符
-spec list2hex(list()) -> list().
list2hex(L) ->
  lists:map(fun(X) -> int2hex(X) end, L).

%% Char值转16进制字符
int2hex(N) when N < 256 ->
  [hex(N div 16), hex(N rem 16)].
hex(N) when N < 10 ->
  $0 + N;
hex(N) when N >= 10, N < 16 ->
  $a + (N - 10).


    """
    with open(os.path.join(project_tools_dir, "convert_utils.erl"), "w", encoding="utf-8") as f:
        f.write(template)

    # 最终写入应用版本内容
    ymd = datetime.now().strftime("%Y.%m.%d")
    utils_template = r"""
{application, utils,
  [{description, "Global System Utils"},
    {vsn, "__VERSION__"},
    {registered, []},
    {applications,
      [
        kernel,
        stdlib,
        crypto
      ]},
    {env, []},
    {modules, []},
    {licenses, ["Apache-2.0"]},
    {links, []}
  ]}.
  """.replace("__VERSION__", ymd)
    with open(os.path.join(project_tools_dir, "utils.app.src"), "w", encoding="utf-8") as f:
        f.write(utils_template)


# 追加加密头文件
def rewrite_crypto_header(project_dir, project_name):
    project_keyboard = str.upper(project_name)
    project_header_dir = os.path.join(project_dir, "include")
    project_header_file = os.path.join(project_header_dir, "crypto_compat.hrl")
    if not os.path.exists(project_header_dir):
        os.mkdir(project_header_dir)

    # 写入 crypto_compat.hrl
    ymd = datetime.now().strftime("%Y%m%d")
    template = r"""
%%%-------------------------------------------------------------------
%%% @author MeteorCat
%%% @copyright (C) 2024, MeteorCat
%%% @doc
%%% 加密库依赖
%%% @end
%%%-------------------------------------------------------------------
-author("MeteorCat").
-include("constant.hrl").

-ifndef(__CRYPTO_NAME__).
-define(__CRYPTO_NAME__, __CRYPTO_VER__).

%%% @doc
%%% HashSha哈希, 示例:
%%%   ?HASH_SHA(<<"abc">>)
%%%     <<169,153,62,54,71,6,129,106,186,62,37,113,120,80,194,108,156,208,216,157>>
%%%   ?HASH_SHA_STR(<<"abc">>)
%%%     "a9993e364706816aba3e25717850c26c9cd0d89d"
%%% @end
-define(HASH_SHA(Data), crypto:hash(sha, Data)).
-define(HASH_FINAL(Data), crypto:hash_final(Data)).
-define(HASH_UPDATE(Data, Salt), crypto:hash_update(Data, Salt)).
-define(HASH_INIT(), crypto:hash_init(sha)).
-define(HASH_SHA_STR(Data), convert_utils:binary2hex(?HASH_SHA(Data))).


%%% @doc
%%% Sha256哈希处理, 示例:
%%%     ?HASH_SHA256(<<"abc">>)
%%%         <<186,120,22,191,143,1,207,234,65,65,64,222,93,174,34,35,176,3,97,163,150,23,122,156,180,16,255,97,242,...>>
%%%     ?HASH_SHA256_STR(<<"abc">>)
%%%         "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
%%% @end
-define(HASH_SHA256(Data), crypto:hash(sha256, Data)).
-define(HASH_SHA256_STR(Data), convert_utils:binary2hex(?HASH_SHA256(Data))).


%%% @doc
%%% Sha512哈希处理, 示例:
%%%     ?HASH_SHA512_STR(<<"abc">>)
%%%         "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"
%%% @end
-define(HASH_SHA512(Data), crypto:hash(sha512, Data)).
-define(HASH_SHA512_STR(Data), convert_utils:binary2hex(?HASH_SHA512(Data))).


%%% @doc
%%% MD5哈希生成
%%% @end
-define(HASH_MD5(Data), erlang:md5(Data)).
-define(HASH_MD5_STR(Data), convert_utils:binary2hex(?HASH_MD5(Data))).

%%% @doc
%%% 生成和验证数字签名
%%% @end
-define(PUB_KEY_SIGN(Alg, Type, Msg, Key), crypto:sign(Alg, Type, Msg, Key)).
-define(PUB_KEY_VERIFY(Alg, Type, Msg, Sig, Key), crypto:verify(Alg, Type, Msg, Sig, Key)).


%%% @doc
%%% HMAC哈希, 示例:
%%%     ?HMAC(sha,<<"MyKey">>,<<"abc">>)
%%%       <<125,2,165,18,252,46,75,79,163,115,9,206,240,213,211,72,79,77,160,71>>
%%%     ?HMAC_STR(sha,<<"MyKey">>,<<"abc">>)
%%%       "7d02a512fc2e4b4fa37309cef0d5d3484f4da047"
%%% @end
-define(HMAC(Type, Key, Data), crypto:mac(hmac, Type, Key, Data)).
-define(HMAC_STR(Type, Key, Data), convert_utils:binary2hex(?HMAC(Type, Type, Data))).

-endif.
""".replace("__CRYPTO_NAME__", "__CRYPTO_" + project_keyboard + "__").replace("__CRYPTO_VER__", ymd)
    with open(project_header_file, "w", encoding="utf-8") as f:
        f.write(template)


# 追加工具库
def rewrite_tools_lib(project_dir, project_name):
    # 不存在目录直接创建
    project_apps_dir = os.path.join(project_dir, "apps")
    project_tools_dir = os.path.join(project_apps_dir, "utils", "src")
    if not os.path.isdir(project_tools_dir):
        os.makedirs(project_tools_dir)

    # 写入基础库工具 - proc
    proc_template = r"""
%%%-------------------------------------------------------------------
%%% @author MeteorCat
%%% @copyright (C) 2024, MeteorCat
%%% @doc
%%% 进程工具
%%% 功能包装, 有的IDE没办法识别 erlang 系列函数需要自己重新包装
%%% @end
%%%-------------------------------------------------------------------
-module(proc_utils).
-author("MeteorCat").
-include("constant.hrl").

%% API
-export([
  proc_id/0,
  proc_alive/1,
  proc_send/2,
  proc_find/1,
  proc_link/1,
  proc_unlink/1,
  proc_proxy/2,
  sleep/1,
  sleep/2
]).

%% @doc 获取当前 Erlang 进程ID
-spec proc_id() -> pid().
proc_id() -> erlang:self().


%% @doc 确认进程存活
-spec proc_alive(Pid) -> boolean() when Pid :: pid().
proc_alive(Pid) -> erlang:is_process_alive(Pid).


%% @doc 进程推送投递
proc_send(Pid, Msg) when erlang:is_pid(Pid) ->
  Pid ! Msg,
  ?true;
proc_send(RegName, Msg) when erlang:is_atom(RegName) ->
  case erlang:whereis(RegName) of
    Pid when erlang:is_pid(Pid) ->
      Pid ! Msg,
      ?true;
    ?undefined -> ?false
  end;
proc_send(?undefined, _Msg) -> ?false;
proc_send(?null, _Msg) -> ?false;
proc_send(_, _Msg) -> ?false.


%% @doc 查找进程
-spec proc_find(term()) -> ?undefined | pid().
proc_find(RegName) -> erlang:whereis(RegName).


%% @doc 进程退出, 发出退出信号
-spec proc_unlink(pid()) -> term().
proc_unlink(Pid) -> erlang:unlink(Pid).


%% @doc 标识进程连接
-spec proc_link(pid()) -> term().
proc_link(Pid) -> erlang:link(Pid).


%% @doc 进程代理执行, 会推送 handle_info({?exec,M,F,A}) 信号
proc_proxy(Pid, {Mod, Func, Args}) when erlang:is_pid(Pid) ->
  proc_send(Pid, {?exec, Mod, Func, Args});
proc_proxy(RegName, {Mod, Func, Args}) when erlang:is_atom(RegName) ->
  case erlang:whereis(RegName) of
    Pid when erlang:is_pid(Pid) ->
      proc_proxy(Pid, {Mod, Func, Args});
    ?undefined -> ?false
  end;
proc_proxy(_, _) ->
  ?false.
  


%% @doc 休眠等待
-spec sleep(MillSec) -> boolean() when MillSec :: integer().
sleep(MillSec) ->
  receive after MillSec -> ?true end.


%% @doc 休眠等待之后执行匿名函数
sleep(MillSec, F) when erlang:is_function(F, 0) ->
  receive after MillSec -> F() end;
sleep(MillSec, _F) ->
  sleep(MillSec).
    """
    with open(os.path.join(project_tools_dir, "proc_utils.erl"), "w", encoding="utf-8") as f:
        f.write(proc_template)

    # 写入基础库工具 - sys
    sys_template = r"""
%%%-------------------------------------------------------------------
%%% @author MeteorCat
%%% @copyright (C) 2024, MeteorCat
%%% @doc
%%% 系统工具
%%% 系统内部需要用到函数方法
%%% @end
%%%-------------------------------------------------------------------
-module(sys_utils).
-author("MeteorCat").
-include("constant.hrl").

%% API
-export([
  cpu_cores/0,
  cpu_core_id/0,
  smp_enable/0,
  os_type/0,
  set_trap_exit/0,
  set_trap_exit/1,
  uniform/0,
  uniform/1,
  rand/2,
  rand_list/1,
  rand_str/1,
  rand_str/2,
  area_id/2,
  guid/2
]).

%% @doc 获取主机目前CPU核心线程, 用于进程分配调度
-spec cpu_cores() -> pos_integer().
cpu_cores() ->
  % erlang:get|put 是 erlang 进程维护的内存表进程会把一起连带数据销毁
  % 所以可以在运行期间可以作为进程当中的临时变量做处理
  case erlang:get(schedulers) of
    ?undefined ->
      Cores = erlang:system_info(schedulers),
      erlang:put(schedulers, Cores),
      Cores;
    Cores -> Cores
  end.
  
  
%% @doc 获取 Erlang 目前运行在CPU哪个核心线程
-spec cpu_core_id() -> pos_integer().
cpu_core_id() ->
  case erlang:get(scheduler_id) of
    ?undefined ->
      CoreIdx = erlang:system_info(scheduler_id),
      erlang:put(scheduler_id, CoreIdx),
      CoreIdx;
    CoreIdx -> CoreIdx
  end.

%% @doc 确认SMP是否启动
-spec smp_enable() -> boolean().
smp_enable() ->
  case erlang:get(smp_support) of
    ?undefined ->
      EnableSmp = erlang:system_info(smp_support),
      erlang:put(smp_support, EnableSmp),
      EnableSmp;
    EnableSmp -> EnableSmp
  end.


%% @doc 获取系统类型
-spec os_type() -> term().
os_type() ->
  case erlang:get(os_type) of
    ?undefined ->
      OsType = erlang:system_info(os_type),
      erlang:put(os_type, OsType),
      OsType;
    OsType -> OsType
  end.
  
%% @doc 设置崩溃拦截
-spec set_trap_exit(?true | ?false) -> term().
set_trap_exit(Flag) -> erlang:process_flag(?trap_exit, Flag).
set_trap_exit() -> erlang:process_flag(?trap_exit, ?true).


%% @doc 替代系统核心随机种子
uniform(N) when erlang:is_integer(N), N >= 1 ->
  erlang:trunc(?MODULE:uniform() * N) + 1.
uniform() ->
  {L1, L2, L3} = case get(random_seed) of
                   ?undefined -> os:timestamp();
                   Tuple -> Tuple
                 end,
  R1 = (L1 * 171) rem 30269,
  R2 = (L2 * 172) rem 30307,
  R3 = (L3 * 170) rem 30323,
  erlang:put(random_seed, {R1, R2, R3}),
  R = L1 / 30269 + L2 / 30307 + L3 / 30323,
  R - erlang:trunc(R).
  
  
%% @doc 产生介于Min到Max之间的随机整数
rand(Same, Same) -> Same;
rand(N1, N2) ->
  {Min, Max} = if N1 >= N2 -> {N2, N1};
                 ?true -> {N1, N2}
               end,
  %% 如果没有种子,将从核心服务器中去获取一个种子,以保证不同进程都可取得不同的种子
  M = Min - 1,
  ?MODULE:uniform(Max - M) + M.
  
  
%% @doc 从列表随机抽取元素
rand_list([]) -> ?null;
rand_list(List) ->
  Len = length(List),
  Idx = ?MODULE:uniform(Len),
  lists:nth(Idx, List).

%% @doc 生成指定长度的随机字符串
rand_str(Len) ->
  rand_str(Len, []).
rand_str(0, Chars) -> Chars;
rand_str(Len, Chars) ->
  Value = 97 + ?MODULE:uniform(25),
  rand_str(Len - 1, [Value | Chars]).
  
  
%% @doc 合并渠道+区服的唯一区域Id, 方便后续合服
%% MajorId 渠道不超过255个, 也就是 0xff
%% MinorId 区服不超过65535, 也就是 0xffff
area_id(MajorId, MinorId) when
  erlang:is_number(MajorId) andalso erlang:is_number(MinorId)
    andalso MajorId =< 255 andalso MinorId =< 65535 ->
  ((MajorId band 255) bsl 16) bor (MinorId band 65535);
area_id(_, _) -> ?unsupported.


%% @doc 生成全局唯一64位id
%% MajorId 渠道ID, 占据 10 bit
%% MinorId 服务器ID, 占 12 bit
%% 时间戳混合占据 41 bit
%% ((millisecond / 1000 + second + 1000) << (12 + 10)) + (MinorId << 10) + MajorId
%% 转化得出: (MS div 1000 + S * 1000) bsl (12+10) + (3(服务器ID) bsl 100) + 1(渠道ID)
%% 公式计算: ((erlang:system_time(millisecond) div 1000 + erlang:system_time(seconds) * 1000) bsl (12+10)) + (10001 bsl 10) + 1.
%% 可以测试下高并发的时候生成唯一标识碰撞概率
guid(MajorId, MinorId) when
  erlang:is_number(MajorId) andalso erlang:is_number(MinorId)
    andalso MajorId =< 255 andalso MinorId =< 65535 ->
  MS = erlang:system_time(millisecond),
  S = erlang:system_time(seconds),
  ((MS div 1000 + S * 1000) bsl (12 + 10)) + (MinorId bsl 10) + MajorId.
    """
    with open(os.path.join(project_tools_dir, "sys_utils.erl"), "w", encoding="utf-8") as f:
        f.write(sys_template)

    # 写入基础库工具 - datetime
    datetime_template = r"""
%%%-------------------------------------------------------------------
%%% @author MeteorCat
%%% @copyright (C) 2024, MeteorCat
%%% @doc
%%% 日期时间工具
%%% @end
%%%-------------------------------------------------------------------
-module(datetime_utils).
-author("MeteorCat").
-include("constant.hrl").

%% API
-export([
  second/0,
  millisecond/0,
  microsecond/0,
  nanosecond/0,
  now/0,
  timestamp/0,
  timestamp_ms/0,
  date/0,
  time/0,
  week_number/0,
  week/0,
  datetime/0,
  date_ymd/0,
  date_yw/0
]).


%% @doc erlang:system_time(second) 别名
-spec second() -> non_neg_integer().
second() -> erlang:system_time(second).


%% @doc erlang:system_time(milli_seconds) 别名
-spec millisecond() -> non_neg_integer().
millisecond() -> erlang:system_time(millisecond).

%% @doc erlang:system_time(microsecond) 别名
-spec microsecond() -> non_neg_integer().
microsecond() -> erlang:system_time(microsecond).

%% @doc erlang:system_time(nanosecond) 别名
-spec nanosecond() -> non_neg_integer().
nanosecond() -> erlang:system_time(nanosecond).


%% @doc 获取时间信息, 老版本 erlang:now 新替代
-spec now() -> erlang:timestamp().
now() -> erlang:timestamp().


%%% @doc 获取时间戳, 秒级
%%% @end
-spec timestamp() -> non_neg_integer().
timestamp() -> second().

%%% @doc 获取时间戳, 毫秒级别
%%% @end
-spec timestamp_ms() -> non_neg_integer().
timestamp_ms() -> millisecond().

%% @doc 获取今年第几周
-spec week_number() -> non_neg_integer().
week_number() ->
  Datetime = ?MODULE:date(), %% 获取年月
  {_Y, WeekNum} = calendar:iso_week_number(Datetime),
  WeekNum.

%% @doc 获取这个星期周几: `1`: Monday, `2`: Tuesday
-spec week() -> non_neg_integer().
week() ->
  Datetime = ?MODULE:date(),
  calendar:day_of_the_week(Datetime).

%% @doc 获取当前年月日 {Y,M,D}
-spec date() -> {calendar:year1970(), calendar:month(), calendar:day()}.
date() ->
  {Date, _Time} = calendar:now_to_local_time(?MODULE:now()),
  Date.

%% @doc 获取当前时分秒 {H,m,s}
-spec time() -> {calendar:hour(), calendar:minute(), calendar:second()}.
time() ->
  {_Date, Time} = calendar:now_to_local_time(?MODULE:now()),
  Time.


%% @doc 获取当前年月日时分秒 {{Y,M,D},{H:I:S}}
-spec datetime() -> calendar:datetime1970().
datetime() -> calendar:now_to_local_time(?MODULE:now()).


%% @doc 获取年月日的 int 值
-spec date_ymd() -> non_neg_integer().
date_ymd() ->
  {Y, M, D} = ?MODULE:date(),
  Y * 10000 + M * 100 + D.


%% @doc 获取年月周的 int 值, 用于计算每周
-spec date_yw() -> non_neg_integer().
date_yw() ->
  Datetime = ?MODULE:date(), %% 获取年月
  {Y, WeekNum} = calendar:iso_week_number(Datetime),
  Y * 100 + WeekNum.
    """
    with open(os.path.join(project_tools_dir, "datetime_utils.erl"), "w", encoding="utf-8") as f:
        f.write(datetime_template)

    # 写入基础库工具 - byte
    byte_template = r"""
%%%-------------------------------------------------------------------
%%% @author MeteorCat
%%% @copyright (C) 2024, MeteorCat
%%% @doc 
%%% 二进制处理库
%%% @end
%%%-------------------------------------------------------------------
-module(byte_utils).
-author("MeteorCat").
-include("constant.hrl").

%% API
-export([
  decode_value/2,
  decode_bytes/2,
  encode_bytes/2,
  encode_list/1,
  encode_unicode_string/1,
  encode_tuples/1
]).

%% @doc 格式消息解码映射
decode_value(<<Val:?i8_t, Rest/?bytes_t>>, ?i8) ->
  {Val, Rest};
decode_value(<<Val:?u8_t, Rest/?bytes_t>>, ?u8) ->
  {Val, Rest};
decode_value(<<Val:?i16_t, Rest/?bytes_t>>, ?i16) ->
  {Val, Rest};
decode_value(<<Val:?u16_t, Rest/?bytes_t>>, ?u16) ->
  {Val, Rest};
decode_value(<<Val:?i32_t, Rest/?bytes_t>>, ?i32) ->
  {Val, Rest};
decode_value(<<Val:?u32_t, Rest/?bytes_t>>, ?u32) ->
  {Val, Rest};
decode_value(<<Val:?i64_t, Rest/?bytes_t>>, ?i64) ->
  {Val, Rest};
decode_value(<<Val:?u64_t, Rest/?bytes_t>>, ?u64) ->
  {Val, Rest};
decode_value(<<Len:?u32_t, Val:Len/?bytes_t, Rest/?bytes_t>>, ?str) ->
  {Val, Rest};
decode_value(Bytes, ?f64) ->
  try <<Val:?f64_t, Next/?bytes_t>> = Bytes, {Val, Next}
  catch _:_ -> <<_:64, NewNext/?bytes_t>> = Bytes, {?nan, NewNext} end.
  

%%% @doc 遍历解构数据
%%% 解包示例: decode_bytes({?i32,?i32},传入二进制数据), 返回数据 {解包数据A,解包数据B,Next}
%%% @end
decode_bytes(Types, Bytes) ->
  Size = erlang:tuple_size(Types),
  Data = erlang:make_tuple(Size + 1, 0),
  decode_bytes(1, Size, Data, Types, Bytes).
decode_bytes(Idx, Size, Data, Types, Bytes) ->
  {Val, Next} = decode_value(Bytes, erlang:element(Idx, Types)),
  if Idx =:= Size ->
    NewData = erlang:setelement(Idx, Data, Val),
    erlang:setelement(Idx + 1, NewData, Next);
    ?true ->
      NewData = erlang:setelement(Idx, Data, Val),
      decode_bytes(Idx + 1, Size, NewData, Types, Next)
  end.
  
  
%% @doc 消息数据封包, [length:uint32_t] [id:uint32_t] [data:bytes]
-spec encode_bytes(non_neg_integer(), bitstring()) -> bitstring().
encode_bytes(Id, Data) ->
  Size = byte_size(Data),
  encode_bytes(Id, Data, Size).
-spec encode_bytes(non_neg_integer(), bitstring(), non_neg_integer()) -> bitstring().
encode_bytes(Id, Data, DataLen) ->
  Size = DataLen,
  <<Size:?u32_t, Id:?u32_t, Data/?bytes_t>>.


%% @doc 打包列表
-spec encode_list(bitstring() | list()) -> bitstring().
encode_list(L) when erlang:is_list(L) ->
  encode_list(convert_utils:to_binary(L));
encode_list(L) when erlang:is_binary(L) ->
  Size = byte_size(L),
  <<Size:?u32_t, L/?bytes_t>>.

%% @doc 打包 unicode 字符串
-spec encode_unicode_string(string()) -> bitstring().
encode_unicode_string(String) ->
  UnicodeString = unicode:characters_to_binary(String),
  encode_list(UnicodeString).


%% @doc 对 [{xxx,yyy,...},...] 对象进行数据编码
%% 留意递归程度不要太深, 这里仅仅作为一维展开
%% 常见于 [ {道具ID-1,道具数量-1},{道具ID-2,道具数量-2} ]
%% 确认是否存在: lists:keyfind(104,1,Awards).
%% 如果不存在添加: lists:keystore(104,1,Awards,{104,1}).
%% 存在则替换: lists:keyreplace(101,1,Awards,{101,100}).
%% 最后编码的二进制: <<列表长度,<<列表占位>>>>
-spec encode_tuples([tuple()]) -> bitstring().
encode_tuples(L) ->
  Size = length(L),
  Res = encode_tuples_1(L, <<>>),
  <<Size:?u32_t, Res/?bytes_t>>.
encode_tuples_1([], Res) -> Res;
encode_tuples_1([Head | Tail], Res) when erlang:is_tuple(Head) ->
  TSize = erlang:size(Head),
  TRes = encode_tuples_2(Head, TSize, 1, <<>>),
  encode_tuples_1(Tail, <<Res/?bytes_t, TRes/?bytes_t>>);
encode_tuples_1([_ | Tail], Res) -> %% 不是元组对象的匹配
  encode_tuples_1(Tail, Res).
encode_tuples_2(_, Size, Idx, Res) when Idx > Size -> Res;
encode_tuples_2(T, Size, Idx, Res) ->
  Element = erlang:element(Idx, T),
  %% 匹配类型
  case Element of
    Element when erlang:is_number(Element) ->
      encode_tuples_2(T, Size, Idx + 1, <<Res/?bytes_t, Element:?u32_t>>);
    Element when erlang:is_binary(Element) ->
      encode_tuples_2(T, Size, Idx + 1, <<Res/?bytes_t, Element/?bytes_t>>);
    _ ->
      encode_tuples_2(T, Size, Idx + 1, Res)
  end.
    """
    with open(os.path.join(project_tools_dir, "byte_utils.erl"), "w", encoding="utf-8") as f:
        f.write(byte_template)


# 入口调用
if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Generate Rebar3 Project")
    parser.add_argument("-p", "--project", help="Project name", required=True)
    parser.add_argument("-d", "--directory", help="Directory path", required=False, default=".")
    parser.add_argument("-lib", "--lib", help="System Utils", action="store_true", required=False)
    parser.add_argument("-n", "--net", help="Network Server[tcp|udp]", required=False, default="none")
    parser.add_argument("-b", "--backup", help="Backup Old Directory", action="store_true", required=False)
    args = parser.parse_args()

    # 确认项目名合法
    if re.fullmatch(r'[\\w]+', args.project) is not None:
        raise Exception("Invalid Project Name")

    # 目录是否存在
    filepath = os.path.join(args.directory, args.project)
    filepath = os.path.abspath(filepath)
    if os.path.exists(filepath):
        if args.backup:
            backup_timestamp = "_back_%d" % time.time()
            backup = filepath + backup_timestamp
            shutil.move(filepath, backup)
        else:
            raise Exception("Directory Exists")

    # 确认当前目录有 rebar3, 没有直接下载
    rebar_file = ROOT_DIR + os.sep + REBAR_EXE
    if not os.path.exists(rebar_file):
        print("Download Rebar3 ......")
        urllib.request.urlretrieve(REBAR_CDN, rebar_file)

    # 确认 Rebar 下载
    if not os.path.exists(rebar_file):
        raise Exception("Rebar3 Download Failed")
    print("Generating Path:", filepath)
    print("Rebar3 Document:", REBAR_DOC)

    # 确认ERL版本
    result = subprocess.run([ERL_EXE, "-version"], capture_output=True)
    if result.returncode != 0:
        raise Exception("Erlang Version Failed")
    erl_version = (result
                   .stderr
                   .decode()
                   .replace(os.sep, ""))
    erl_version = erl_version[erl_version.find("version") + len("version")::].strip()
    print("Erl Emulator:", erl_version)

    # 启动 rebar 构建
    os.chdir(args.directory)
    result = subprocess.call([ERL_SCRIPT, rebar_file, "new", "umbrella", args.project])
    if result != 0 or not os.path.exists(filepath):
        raise Exception("Rebar3 Generated Failed")

    # 复制rebar3到内部
    shutil.copy2(rebar_file, str(filepath))

    # 切换目录重写配置文件
    os.chdir(filepath)
    rewrite_rebar_config(filepath, args.project)
    rewrite_app_config(filepath, args.project)
    rewrite_constant_header(filepath, args.project)
    rewrite_convert_erl(filepath, args.project)
    rewrite_crypto_header(filepath, args.project)

    # 可选项, 工具库
    if args.lib:
        rewrite_tools_lib(filepath, args.project)

    # 可选项, 日志库


    # 数据库驱动, emysql
    # emysql目前新版本24+有问题, 因为Erlang新版本ABI变动导致结构体变动
    # 这里追加高版本处理:
    # -export_type([
    #   gb_tree/0,
    #   queue/0,
    #   dict/0
    # ]).
    #
    # -ifdef(namespaced_types).
    # -type gb_tree() :: gb_trees:tree().
    # -type queue() :: queue:queue().
    # -type dict() :: dict:dict().
    # -else.
    # -type gb_tree() :: gb_tree().
    # -type queue() :: queue().
    # -type dict() :: dict().
    # -endif.
    # 以上就是为了处理新旧版本的 ABI 不一致所需构建全局宏
    # 实际上最正确的做法是 fork 官方版本 Github 版本追加修改头文件处理

后续直接构建项目:

# 打包出 test 项目
python GenRebar3Project.py -p test

Update: 更新Github代码库, 代码库地址