feat: 初步实现 init 项目等项目生命周期工具
This commit is contained in:
parent
002b51b71c
commit
4c0522c067
@ -1,14 +1,12 @@
|
|||||||
package org.jcnc.snow.cli;
|
package org.jcnc.snow.cli;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* 所有 CLI 子命令(如 compile、run 等)都必须实现的命令接口。
|
||||||
* <p>
|
* <p>
|
||||||
* 所有子命令(如 compile、run 等)都必须实现的命令接口。
|
* 实现类应为无状态(stateless)、线程安全(thread-safe)。
|
||||||
|
* 可通过 {@link java.util.ServiceLoader ServiceLoader} 自动发现,
|
||||||
|
* 或直接在 {@link SnowCLI} 中注册。
|
||||||
* </p>
|
* </p>
|
||||||
* <ul>
|
|
||||||
* <li>实现类应当是无状态(stateless)、线程安全(thread-safe)的。</li>
|
|
||||||
* <li>可通过 {@link java.util.ServiceLoader ServiceLoader} 自动发现,或直接在 {@link SnowCLI} 注册。</li>
|
|
||||||
* </ul>
|
|
||||||
* <p>
|
|
||||||
*/
|
*/
|
||||||
public interface CLICommand {
|
public interface CLICommand {
|
||||||
|
|
||||||
@ -28,16 +26,19 @@ public interface CLICommand {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 打印命令的专用 usage 信息(可选实现)。
|
* 打印命令的专用 usage 信息(可选实现)。
|
||||||
|
* <p>
|
||||||
* 可覆盖此方法自定义帮助信息,默认无操作。
|
* 可覆盖此方法自定义帮助信息,默认无操作。
|
||||||
|
* </p>
|
||||||
*/
|
*/
|
||||||
default void printUsage() {
|
default void printUsage() {
|
||||||
|
// 默认实现为空,可由子类覆盖
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行命令逻辑。
|
* 执行命令逻辑。
|
||||||
*
|
*
|
||||||
* @param args 传递给子命令的参数(不含命令名本身)
|
* @param args 传递给子命令的参数(不含命令名本身)
|
||||||
* @return 进程退出码(0 为成功,非0为错误)
|
* @return 进程退出码(0 为成功,非 0 为错误)
|
||||||
* @throws Exception 可抛出任意异常,框架会统一捕获和输出
|
* @throws Exception 可抛出任意异常,框架会统一捕获和输出
|
||||||
*/
|
*/
|
||||||
int execute(String[] args) throws Exception;
|
int execute(String[] args) throws Exception;
|
||||||
|
|||||||
@ -32,7 +32,12 @@ public class SnowCLI {
|
|||||||
private static final Map<String, Supplier<CLICommand>> COMMANDS = Map.of(
|
private static final Map<String, Supplier<CLICommand>> COMMANDS = Map.of(
|
||||||
"compile", CompileCommand::new,
|
"compile", CompileCommand::new,
|
||||||
"run", RunCommand::new,
|
"run", RunCommand::new,
|
||||||
"version", VersionCommand::new
|
"version", VersionCommand::new,
|
||||||
|
"init", org.jcnc.snow.cli.commands.InitCommand::new,
|
||||||
|
"build", org.jcnc.snow.cli.commands.BuildCommand::new,
|
||||||
|
"install", org.jcnc.snow.cli.commands.InstallCommand::new,
|
||||||
|
"publish", org.jcnc.snow.cli.commands.PublishCommand::new,
|
||||||
|
"clean", org.jcnc.snow.cli.commands.CleanCommand::new
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -89,4 +94,4 @@ public class SnowCLI {
|
|||||||
System.exit(1);
|
System.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
86
src/main/java/org/jcnc/snow/cli/commands/BuildCommand.java
Normal file
86
src/main/java/org/jcnc/snow/cli/commands/BuildCommand.java
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
package org.jcnc.snow.cli.commands;
|
||||||
|
|
||||||
|
import org.jcnc.snow.cli.CLICommand;
|
||||||
|
import org.jcnc.snow.pkg.dsl.CloudDSLParser;
|
||||||
|
import org.jcnc.snow.pkg.lifecycle.LifecycleManager;
|
||||||
|
import org.jcnc.snow.pkg.lifecycle.LifecyclePhase;
|
||||||
|
import org.jcnc.snow.pkg.model.Project;
|
||||||
|
import org.jcnc.snow.pkg.resolver.DependencyResolver;
|
||||||
|
import org.jcnc.snow.pkg.tasks.CompileTask;
|
||||||
|
import org.jcnc.snow.pkg.tasks.PackageTask;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CLI 命令:构建当前项目(包含依赖解析、编译、打包)。
|
||||||
|
* <p>
|
||||||
|
* 该命令会依次执行依赖解析、源码编译和产物打包阶段。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* 用法示例:
|
||||||
|
* $ snow build
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public final class BuildCommand implements CLICommand {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回命令名称,用于 CLI 调用。
|
||||||
|
*
|
||||||
|
* @return 命令名称,如 "build"
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String name() {
|
||||||
|
return "build";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回命令简介,用于 CLI 帮助或命令列表展示。
|
||||||
|
*
|
||||||
|
* @return 命令描述字符串
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String description() {
|
||||||
|
return "Build the current project by resolving dependencies, compiling, and packaging in sequence.";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印命令用法信息。
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void printUsage() {
|
||||||
|
System.out.println("Usage: snow build ");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行项目构建流程。
|
||||||
|
* <ul>
|
||||||
|
* <li>解析项目描述文件(project.cloud)</li>
|
||||||
|
* <li>依赖解析(RESOLVE_DEPENDENCIES)</li>
|
||||||
|
* <li>源码编译(COMPILE)</li>
|
||||||
|
* <li>产物打包(PACKAGE)</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param args CLI 传入的参数数组
|
||||||
|
* @return 执行结果码(0 表示成功)
|
||||||
|
* @throws Exception 执行过程中出现错误时抛出
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int execute(String[] args) throws Exception {
|
||||||
|
|
||||||
|
Path dslFile = Paths.get("project.cloud");
|
||||||
|
Project project = CloudDSLParser.parse(dslFile);
|
||||||
|
DependencyResolver resolver = new DependencyResolver(Paths.get(System.getProperty("user.home"), ".snow", "cache"));
|
||||||
|
LifecycleManager lm = new LifecycleManager();
|
||||||
|
|
||||||
|
// 注册各阶段任务
|
||||||
|
lm.register(LifecyclePhase.RESOLVE_DEPENDENCIES, () -> resolver.resolve(project));
|
||||||
|
lm.register(LifecyclePhase.COMPILE, new CompileTask(project));
|
||||||
|
lm.register(LifecyclePhase.PACKAGE, new PackageTask(project));
|
||||||
|
|
||||||
|
lm.executeAll();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
63
src/main/java/org/jcnc/snow/cli/commands/CleanCommand.java
Normal file
63
src/main/java/org/jcnc/snow/cli/commands/CleanCommand.java
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package org.jcnc.snow.cli.commands;
|
||||||
|
|
||||||
|
import org.jcnc.snow.cli.CLICommand;
|
||||||
|
import org.jcnc.snow.pkg.lifecycle.LifecycleManager;
|
||||||
|
import org.jcnc.snow.pkg.lifecycle.LifecyclePhase;
|
||||||
|
import org.jcnc.snow.pkg.tasks.CleanTask;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CLI 命令:清理构建输出和本地缓存目录。
|
||||||
|
* <p>
|
||||||
|
* 用于清除项目生成的 build、dist 等中间产物,保持工作区整洁。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* 用法示例:
|
||||||
|
* $ snow clean
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public final class CleanCommand implements CLICommand {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回命令名称,用于 CLI 调用。
|
||||||
|
*
|
||||||
|
* @return 命令名称,如 "clean"
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String name() {
|
||||||
|
return "clean";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回命令简介,用于 CLI 帮助或命令列表展示。
|
||||||
|
*
|
||||||
|
* @return 命令描述字符串
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String description() {
|
||||||
|
return "Clean build outputs and local cache, remove intermediate artifacts, and free disk space.";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印命令用法信息。
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void printUsage() {
|
||||||
|
System.out.println("Usage: snow clean ");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行清理任务。
|
||||||
|
*
|
||||||
|
* @param args CLI 传入的参数数组
|
||||||
|
* @return 执行结果码(0 表示成功)
|
||||||
|
* @throws Exception 执行过程中出现错误时抛出
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int execute(String[] args) throws Exception {
|
||||||
|
LifecycleManager lm = new LifecycleManager();
|
||||||
|
lm.register(LifecyclePhase.CLEAN, new CleanTask());
|
||||||
|
lm.executeAll();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -24,20 +24,17 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* CLI 命令:将 .snow 源文件编译为 VM 字节码(.water 文件)。
|
||||||
* <p>
|
* <p>
|
||||||
* 编译器子命令:<code>snow compile</code><br>
|
* 支持递归目录、多文件编译,可选编译后立即运行。<br>
|
||||||
* 将一个或多个 .snow 源文件编译为 VM 字节码文件(.water)。
|
* 命令参数支持 run、-o、-d 等。
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <ul>
|
* <pre>
|
||||||
* <li>-o 指定输出基名(去掉 .water 后缀)</li>
|
* 用法示例:
|
||||||
* <li>-d 递归目录编译(输出名自动取目录名)</li>
|
* $ snow compile [run] [-o <name>] [-d <srcDir>] [file1.snow file2.snow …]
|
||||||
* <li>run 子命令:编译完成立即运行 VM</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* <pre>用法:
|
|
||||||
* snow compile [run] [-o <name>] [-d <srcDir>] [file1.snow file2.snow …]
|
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public final class CompileCommand implements CLICommand {
|
public final class CompileCommand implements CLICommand {
|
||||||
|
|||||||
78
src/main/java/org/jcnc/snow/cli/commands/InitCommand.java
Normal file
78
src/main/java/org/jcnc/snow/cli/commands/InitCommand.java
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package org.jcnc.snow.cli.commands;
|
||||||
|
|
||||||
|
import org.jcnc.snow.cli.CLICommand;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CLI 命令:创建新的项目骨架。
|
||||||
|
* <p>
|
||||||
|
* 用于快速初始化标准目录结构和 DSL 配置文件(project.cloud)。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* 用法示例:
|
||||||
|
* $ snow init
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public final class InitCommand implements CLICommand {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回命令名称,用于 CLI 调用。
|
||||||
|
*
|
||||||
|
* @return 命令名称,如 "init"
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String name() {
|
||||||
|
return "init";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回命令简介,用于 CLI 帮助或命令列表展示。
|
||||||
|
*
|
||||||
|
* @return 命令描述字符串
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String description() {
|
||||||
|
return "Initialize a new project skeleton with directory structure and project.cloud file.";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印命令用法信息。
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void printUsage() {
|
||||||
|
System.out.println("Usage: snow init [--lang <lang>]");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行项目初始化流程,创建 src 目录和 DSL 配置文件。
|
||||||
|
*
|
||||||
|
* @param args CLI 传入的参数数组
|
||||||
|
* @return 执行结果码(0 表示成功)
|
||||||
|
* @throws Exception 文件创建过程中出现错误时抛出
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int execute(String[] args) throws Exception {
|
||||||
|
// 生成 skeleton `.cloud` 文件和 src 目录
|
||||||
|
Path dir = Paths.get(".").toAbsolutePath();
|
||||||
|
Files.createDirectories(dir.resolve("src"));
|
||||||
|
Path dsl = dir.resolve("project.cloud");
|
||||||
|
if (Files.notExists(dsl)) {
|
||||||
|
Files.writeString(dsl, """
|
||||||
|
# Generated by snow init
|
||||||
|
project {
|
||||||
|
group = "com.example"
|
||||||
|
artifact = "demo-app"
|
||||||
|
version = "0.0.1-SNAPSHOT"
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
System.out.println("[init] created " + dsl);
|
||||||
|
} else {
|
||||||
|
System.out.println("[init] project.cloud already exists");
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
65
src/main/java/org/jcnc/snow/cli/commands/InstallCommand.java
Normal file
65
src/main/java/org/jcnc/snow/cli/commands/InstallCommand.java
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package org.jcnc.snow.cli.commands;
|
||||||
|
|
||||||
|
import org.jcnc.snow.cli.CLICommand;
|
||||||
|
import org.jcnc.snow.pkg.dsl.CloudDSLParser;
|
||||||
|
import org.jcnc.snow.pkg.model.Project;
|
||||||
|
import org.jcnc.snow.pkg.resolver.DependencyResolver;
|
||||||
|
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CLI 命令:解析并下载项目依赖到本地缓存。
|
||||||
|
* <p>
|
||||||
|
* 适用于离线使用和依赖预热场景,会自动读取项目描述文件并处理依赖缓存。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* 用法示例:
|
||||||
|
* $ snow install
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public final class InstallCommand implements CLICommand {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回命令名称,用于 CLI 调用。
|
||||||
|
*
|
||||||
|
* @return 命令名称,如 "install"
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String name() {
|
||||||
|
return "install";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回命令简介,用于 CLI 帮助或命令列表展示。
|
||||||
|
*
|
||||||
|
* @return 命令描述字符串
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String description() {
|
||||||
|
return "Resolve and download project dependencies to local cache for offline development or faster builds.";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印命令用法信息。
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void printUsage() {
|
||||||
|
System.out.println("Usage: snow install ");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行依赖解析和下载任务。
|
||||||
|
*
|
||||||
|
* @param args CLI 传入的参数数组
|
||||||
|
* @return 执行结果码(0 表示成功)
|
||||||
|
* @throws Exception 解析或下载依赖过程中出现错误时抛出
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int execute(String[] args) throws Exception {
|
||||||
|
Project project = CloudDSLParser.parse(Paths.get("project.cloud"));
|
||||||
|
DependencyResolver resolver = new DependencyResolver(Paths.get(System.getProperty("user.home"), ".snow", "cache"));
|
||||||
|
resolver.resolve(project);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
74
src/main/java/org/jcnc/snow/cli/commands/PublishCommand.java
Normal file
74
src/main/java/org/jcnc/snow/cli/commands/PublishCommand.java
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package org.jcnc.snow.cli.commands;
|
||||||
|
|
||||||
|
import org.jcnc.snow.cli.CLICommand;
|
||||||
|
import org.jcnc.snow.pkg.dsl.CloudDSLParser;
|
||||||
|
import org.jcnc.snow.pkg.lifecycle.LifecycleManager;
|
||||||
|
import org.jcnc.snow.pkg.lifecycle.LifecyclePhase;
|
||||||
|
import org.jcnc.snow.pkg.model.Project;
|
||||||
|
import org.jcnc.snow.pkg.tasks.PublishTask;
|
||||||
|
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CLI 命令:将已构建的项目包发布到远程仓库。
|
||||||
|
* <p>
|
||||||
|
* 用于持续集成、交付或分发场景。
|
||||||
|
* 支持自动读取 DSL 项目描述文件,并注册和执行发布生命周期阶段的任务。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* 用法示例:
|
||||||
|
* $ snow publish
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public final class PublishCommand implements CLICommand {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回该命令的名称(用于 CLI 调用)。
|
||||||
|
*
|
||||||
|
* @return 命令名称字符串,如 "publish"
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String name() {
|
||||||
|
return "publish";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回命令简介,用于 CLI 帮助或命令列表展示。
|
||||||
|
*
|
||||||
|
* @return 命令描述字符串
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String description() {
|
||||||
|
return "Publish the built package to a remote repository, suitable for continuous integration, delivery, or project distribution.";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印命令用法信息,供终端用户参考。
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void printUsage() {
|
||||||
|
System.out.println("Usage: snow publish ");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行发布命令。
|
||||||
|
* <ul>
|
||||||
|
* <li>解析项目描述文件(如 project.cloud)</li>
|
||||||
|
* <li>注册并执行 PUBLISH 阶段的任务</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param args CLI 传入的参数数组
|
||||||
|
* @return 执行结果码(0表示成功)
|
||||||
|
* @throws Exception 执行过程中出现错误时抛出
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int execute(String[] args) throws Exception {
|
||||||
|
Project project = CloudDSLParser.parse(Paths.get("project.cloud"));
|
||||||
|
LifecycleManager lm = new LifecycleManager();
|
||||||
|
lm.register(LifecyclePhase.PUBLISH, new PublishTask(project));
|
||||||
|
lm.executeAll();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,26 +4,22 @@ import org.jcnc.snow.cli.CLICommand;
|
|||||||
import org.jcnc.snow.vm.VMLauncher;
|
import org.jcnc.snow.vm.VMLauncher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* CLI 命令:运行已编译的 VM 字节码文件(.water)。
|
||||||
* <p>
|
* <p>
|
||||||
* 命令实现:`snow run`
|
* 用于执行 VM 程序文件。支持传递额外 VM 参数,实际运行由 {@link VMLauncher#main(String[])} 完成。
|
||||||
* <br>
|
|
||||||
* 用于运行已编译的 VM 字节码文件(.water)。
|
|
||||||
* </p>
|
|
||||||
* <ul>
|
|
||||||
* <li>支持传递额外 VM 参数。</li>
|
|
||||||
* <li>实际执行通过 {@link VMLauncher#main(String[])} 入口完成。</li>
|
|
||||||
* </ul>
|
|
||||||
* <p>
|
|
||||||
* 用法:<br>
|
|
||||||
* <code>snow run program.water</code>
|
|
||||||
* </p>
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* 用法示例:
|
||||||
|
* $ snow run program.water [additional VM options]
|
||||||
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public final class RunCommand implements CLICommand {
|
public final class RunCommand implements CLICommand {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取命令名。
|
* 返回命令名,用于 CLI 调用。
|
||||||
*
|
*
|
||||||
* @return "run"
|
* @return 命令名称字符串("run")
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String name() {
|
public String name() {
|
||||||
@ -31,9 +27,9 @@ public final class RunCommand implements CLICommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取命令描述。
|
* 返回命令简介,用于 CLI 帮助或命令列表展示。
|
||||||
*
|
*
|
||||||
* @return 命令简介
|
* @return 命令描述字符串
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String description() {
|
public String description() {
|
||||||
@ -51,11 +47,11 @@ public final class RunCommand implements CLICommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行 run 命令,运行 VM 指令文件。
|
* 执行 run 命令,运行指定的 VM 字节码文件。
|
||||||
*
|
*
|
||||||
* @param args 剩余参数(不含命令名),第一个应为 .vm 文件路径
|
* @param args 剩余参数(不含命令名),第一个应为 .water 文件路径,其后为可选 VM 参数
|
||||||
* @return 0 表示成功,1 表示参数错误
|
* @return 0 表示执行成功,1 表示参数错误
|
||||||
* @throws Exception 运行 VM 时可能抛出的异常
|
* @throws Exception VM 启动或执行过程中可能抛出的异常
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int execute(String[] args) throws Exception {
|
public int execute(String[] args) throws Exception {
|
||||||
|
|||||||
@ -4,18 +4,22 @@ import org.jcnc.snow.cli.CLICommand;
|
|||||||
import org.jcnc.snow.cli.SnowCLI;
|
import org.jcnc.snow.cli.SnowCLI;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* CLI 子命令:输出当前 Snow 工具的版本号。
|
||||||
* <p>
|
* <p>
|
||||||
* 子命令实现:`snow version`
|
* 用于显示当前 CLI 工具版本,便于诊断、升级、兼容性确认等场景。
|
||||||
* <br>
|
|
||||||
* 用于打印当前 Snow 的版本号。
|
|
||||||
* </p>
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* 用法示例:
|
||||||
|
* $ snow version
|
||||||
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public final class VersionCommand implements CLICommand {
|
public final class VersionCommand implements CLICommand {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取命令名。
|
* 返回命令名,用于 CLI 调用。
|
||||||
*
|
*
|
||||||
* @return "version"
|
* @return 命令名称字符串("version")
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String name() {
|
public String name() {
|
||||||
@ -23,9 +27,9 @@ public final class VersionCommand implements CLICommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取命令描述。
|
* 返回命令简介,用于 CLI 帮助或命令列表展示。
|
||||||
*
|
*
|
||||||
* @return 命令简介
|
* @return 命令描述字符串
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String description() {
|
public String description() {
|
||||||
@ -49,8 +53,7 @@ public final class VersionCommand implements CLICommand {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int execute(String[] args) {
|
public int execute(String[] args) {
|
||||||
System.out.println("snow version " + "\"" + SnowCLI.SNOW_VERSION + "\"");
|
System.out.println("snow version \"" + SnowCLI.SNOW_VERSION + "\"");
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
125
src/main/java/org/jcnc/snow/pkg/dsl/CloudDSLParser.java
Normal file
125
src/main/java/org/jcnc/snow/pkg/dsl/CloudDSLParser.java
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
package org.jcnc.snow.pkg.dsl;
|
||||||
|
|
||||||
|
import org.jcnc.snow.pkg.model.Project;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CloudDSLParser — .cloud 配置文件解析器
|
||||||
|
* <p>
|
||||||
|
* 作用:
|
||||||
|
* - 读取 Snow 构建工具自定义的 .cloud 文件
|
||||||
|
* - 将内容转换为内存中的 {@link Project} 模型
|
||||||
|
* <p>
|
||||||
|
* 解析规则(:
|
||||||
|
* <p>
|
||||||
|
* 1. 顶级区块(project、properties、repositories、dependencies、build)
|
||||||
|
* 以 “sectionName {” 开始,以 “}” 结束。
|
||||||
|
* <p>
|
||||||
|
* 2. 区块内部识别 “key = value” 形式的赋值。
|
||||||
|
* <p>
|
||||||
|
* 3. build 区块允许嵌套;内部键通过 “.” 展平,
|
||||||
|
* 例如 compile.enabled = true。
|
||||||
|
* <p>
|
||||||
|
*/
|
||||||
|
public final class CloudDSLParser {
|
||||||
|
|
||||||
|
/* ---------- 正则表达式 ---------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 匹配 “sectionName {” 形式的行
|
||||||
|
*/
|
||||||
|
private static final Pattern SECTION_HEADER = Pattern.compile("^(\\w+)\\s*\\{\\s*$");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 匹配 “key = value” 行。
|
||||||
|
* value 允许空格,并忽略行尾 “# …” 注释。
|
||||||
|
*/
|
||||||
|
private static final Pattern KEY_VALUE = Pattern.compile("^(\\w+)\\s*=\\s*([^#]+?)\\s*(?:#.*)?$");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 匹配仅包含 “}” 的行(可有前后空白)
|
||||||
|
*/
|
||||||
|
private static final Pattern BLOCK_END = Pattern.compile("^}\\s*$");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 私有构造,禁止实例化
|
||||||
|
*/
|
||||||
|
private CloudDSLParser() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析指定 .cloud 文件并生成 {@link Project} 对象。
|
||||||
|
*
|
||||||
|
* @param path 文件路径
|
||||||
|
* @return 解析后的 Project
|
||||||
|
* @throws IOException 文件读取失败
|
||||||
|
* @throws IllegalStateException 语法错误(如括号不匹配、未知语句)
|
||||||
|
*/
|
||||||
|
public static Project parse(Path path) throws IOException {
|
||||||
|
|
||||||
|
Deque<String> sectionStack = new ArrayDeque<>(); // 记录当前区块层级
|
||||||
|
Map<String, String> flatMap = new LinkedHashMap<>(); // 扁平化 key → value
|
||||||
|
|
||||||
|
List<String> lines = Files.readAllLines(path);
|
||||||
|
|
||||||
|
int lineNo = 0;
|
||||||
|
for (String raw : lines) {
|
||||||
|
lineNo++;
|
||||||
|
String line = raw.trim();
|
||||||
|
|
||||||
|
/* 1. 跳过空行和注释 */
|
||||||
|
if (line.isEmpty() || line.startsWith("#")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. 区块起始:sectionName { */
|
||||||
|
Matcher sec = SECTION_HEADER.matcher(line);
|
||||||
|
if (sec.matches()) {
|
||||||
|
sectionStack.push(sec.group(1));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. 区块结束:} */
|
||||||
|
if (BLOCK_END.matcher(line).matches()) {
|
||||||
|
if (sectionStack.isEmpty()) {
|
||||||
|
throw new IllegalStateException("第 " + lineNo + " 行出现未配对的 '}'");
|
||||||
|
}
|
||||||
|
sectionStack.pop();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 4. 键值对:key = value */
|
||||||
|
Matcher kv = KEY_VALUE.matcher(line);
|
||||||
|
if (kv.matches()) {
|
||||||
|
String key = kv.group(1).trim();
|
||||||
|
String value = kv.group(2).trim();
|
||||||
|
|
||||||
|
/* 4.1 计算前缀(栈倒序,即从外到内) */
|
||||||
|
String prefix = String.join(".", (Iterable<String>) sectionStack::descendingIterator);
|
||||||
|
if (!prefix.isEmpty()) {
|
||||||
|
key = prefix + "." + key;
|
||||||
|
}
|
||||||
|
|
||||||
|
flatMap.put(key, value);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 5. 无法识别的行 */
|
||||||
|
throw new IllegalStateException("无法解析第 " + lineNo + " 行: " + raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 6. 检查括号是否全部闭合 */
|
||||||
|
if (!sectionStack.isEmpty()) {
|
||||||
|
throw new IllegalStateException("文件结束但区块未闭合:" + sectionStack);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 7. 构造 Project 模型 */
|
||||||
|
return Project.fromFlatMap(flatMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
package org.jcnc.snow.pkg.lifecycle;
|
||||||
|
|
||||||
|
import org.jcnc.snow.pkg.tasks.Task;
|
||||||
|
|
||||||
|
import java.util.EnumMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理不同生命周期阶段与其对应任务的工具类。
|
||||||
|
* <p>
|
||||||
|
* 可为每个 {@link LifecyclePhase} 注册对应的 {@link Task},并按阶段顺序执行所有任务。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* 示例用法:
|
||||||
|
* LifecycleManager manager = new LifecycleManager();
|
||||||
|
* manager.register(LifecyclePhase.INIT, new InitTask());
|
||||||
|
* manager.executeAll();
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public final class LifecycleManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 存储生命周期阶段与对应任务的映射关系。
|
||||||
|
*/
|
||||||
|
private final Map<LifecyclePhase, Task> tasks = new EnumMap<>(LifecyclePhase.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为指定生命周期阶段注册任务。
|
||||||
|
* <p>若该阶段已有任务,则会被新任务覆盖。</p>
|
||||||
|
*
|
||||||
|
* @param phase 生命周期阶段,不能为空
|
||||||
|
* @param task 对应任务,不能为空
|
||||||
|
* @throws NullPointerException 若 phase 或 task 为 null
|
||||||
|
*/
|
||||||
|
public void register(LifecyclePhase phase, Task task) {
|
||||||
|
if (phase == null) {
|
||||||
|
throw new NullPointerException("Lifecycle phase must not be null");
|
||||||
|
}
|
||||||
|
if (task == null) {
|
||||||
|
throw new NullPointerException("Task must not be null");
|
||||||
|
}
|
||||||
|
tasks.put(phase, task);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按 {@link LifecyclePhase} 声明顺序依次执行所有已注册任务。
|
||||||
|
* <p>
|
||||||
|
* 未注册任务的阶段将被跳过。任务执行前会打印阶段名。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @throws Exception 若某个任务执行时抛出异常,将直接抛出并终止后续任务执行
|
||||||
|
*/
|
||||||
|
public void executeAll() throws Exception {
|
||||||
|
for (LifecyclePhase phase : LifecyclePhase.values()) {
|
||||||
|
Task task = tasks.get(phase);
|
||||||
|
if (task != null) {
|
||||||
|
System.out.println(">>> Phase: " + phase);
|
||||||
|
task.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
package org.jcnc.snow.pkg.lifecycle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定义了典型软件包生命周期的各个阶段。
|
||||||
|
*/
|
||||||
|
public enum LifecyclePhase {
|
||||||
|
/** 初始化阶段 */
|
||||||
|
INIT,
|
||||||
|
/** 解析依赖阶段 */
|
||||||
|
RESOLVE_DEPENDENCIES,
|
||||||
|
/** 编译阶段 */
|
||||||
|
COMPILE,
|
||||||
|
/** 打包阶段 */
|
||||||
|
PACKAGE,
|
||||||
|
/** 发布阶段 */
|
||||||
|
PUBLISH,
|
||||||
|
/** 清理阶段 */
|
||||||
|
CLEAN
|
||||||
|
}
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
package org.jcnc.snow.pkg.model;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建配置对象,封装构建过程中的所有选项。
|
||||||
|
* <p>
|
||||||
|
* 支持基于模板变量(形如{@code @{key}})的选项值替换。
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
public final class BuildConfiguration {
|
||||||
|
|
||||||
|
/** 存储配置项的键值对 */
|
||||||
|
private final Map<String, String> options;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 私有构造函数,用于初始化配置项。
|
||||||
|
*
|
||||||
|
* @param options 配置项键值对
|
||||||
|
*/
|
||||||
|
private BuildConfiguration(Map<String, String> options) {
|
||||||
|
this.options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基于原始配置项和变量属性创建配置对象。
|
||||||
|
* <p>
|
||||||
|
* 会将原始配置中的所有值中的{@code @{key}}模板,替换为属性props中对应的值。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param flat 原始的配置项,值中可包含模板变量(如@{name})
|
||||||
|
* @param props 用于替换模板变量的属性集
|
||||||
|
* @return 构建完成的配置对象
|
||||||
|
*/
|
||||||
|
public static BuildConfiguration fromFlatMap(Map<String, String> flat, Map<String, String> props) {
|
||||||
|
Map<String, String> resolved = new HashMap<>();
|
||||||
|
for (Map.Entry<String, String> e : flat.entrySet()) {
|
||||||
|
String value = e.getValue();
|
||||||
|
for (Map.Entry<String, String> p : props.entrySet()) {
|
||||||
|
value = value.replace("@{" + p.getKey() + "}", p.getValue());
|
||||||
|
}
|
||||||
|
resolved.put(e.getKey(), value);
|
||||||
|
}
|
||||||
|
return new BuildConfiguration(resolved);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定key对应的配置值。
|
||||||
|
*
|
||||||
|
* @param key 配置项名称
|
||||||
|
* @param def 默认值(若未找到key则返回此值)
|
||||||
|
* @return 配置项对应值,若不存在则返回默认值
|
||||||
|
*/
|
||||||
|
public String get(String key, String def) {
|
||||||
|
return options.getOrDefault(key, def);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return options.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
82
src/main/java/org/jcnc/snow/pkg/model/Dependency.java
Normal file
82
src/main/java/org/jcnc/snow/pkg/model/Dependency.java
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
package org.jcnc.snow.pkg.model;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表示一个 Maven 风格的 group:artifact:version 依赖坐标。
|
||||||
|
* <p>
|
||||||
|
* 支持通过占位符和属性映射进行动态变量替换,可用于构建工具或依赖管理场景。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* 示例:
|
||||||
|
* Dependency dep = Dependency.fromString(
|
||||||
|
* "core", "com.example:core:@{version}",
|
||||||
|
* Map.of("version", "1.2.3")
|
||||||
|
* );
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public record Dependency(
|
||||||
|
String id,
|
||||||
|
String group,
|
||||||
|
String artifact,
|
||||||
|
String version
|
||||||
|
) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于匹配 group:artifact:version 格式的正则表达式。
|
||||||
|
*/
|
||||||
|
private static final Pattern GAV = Pattern.compile("([^:]+):([^:]+):(.+)");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据字符串坐标和属性映射创建依赖对象。
|
||||||
|
* <p>
|
||||||
|
* 坐标中的占位符如 {@code @{key}} 会用 props 中对应的值替换。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param id 依赖唯一标识
|
||||||
|
* @param coordinate 依赖坐标字符串,格式为 group:artifact:version,支持变量占位符
|
||||||
|
* @param props 占位符替换的属性映射
|
||||||
|
* @return 解析后的 Dependency 实例
|
||||||
|
* @throws IllegalArgumentException 如果坐标格式非法
|
||||||
|
*/
|
||||||
|
public static Dependency fromString(String id, String coordinate, Map<String, String> props) {
|
||||||
|
// 替换 @{prop} 占位符
|
||||||
|
String resolved = coordinate;
|
||||||
|
for (Map.Entry<String, String> p : props.entrySet()) {
|
||||||
|
resolved = resolved.replace("@{" + p.getKey() + "}", p.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
Matcher m = GAV.matcher(resolved);
|
||||||
|
if (!m.matches()) {
|
||||||
|
throw new IllegalArgumentException("Invalid dependency format: " + coordinate);
|
||||||
|
}
|
||||||
|
return new Dependency(id, m.group(1), m.group(2), m.group(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成依赖对应的标准仓库 jar 路径。
|
||||||
|
* <p>
|
||||||
|
* 路径格式通常为:groupId/artifactId/version/artifactId-version.jar<br>
|
||||||
|
* 例如:com/example/core/1.2.3/core-1.2.3.jar
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @return 仓库 jar 文件的相对路径
|
||||||
|
*/
|
||||||
|
public String toPath() {
|
||||||
|
String groupPath = group.replace('.', '/');
|
||||||
|
return groupPath + "/" + artifact + "/" + version + "/" + artifact + "-" + version + ".jar";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回该依赖的 group:artifact:version 字符串表示。
|
||||||
|
*
|
||||||
|
* @return Maven 坐标字符串
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return group + ":" + artifact + ":" + version;
|
||||||
|
}
|
||||||
|
}
|
||||||
193
src/main/java/org/jcnc/snow/pkg/model/Project.java
Normal file
193
src/main/java/org/jcnc/snow/pkg/model/Project.java
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
package org.jcnc.snow.pkg.model;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表示一个软件包/模块的项目信息,包括元数据、属性、仓库、依赖和构建配置等。
|
||||||
|
* <p>
|
||||||
|
* 本类为不可变对象,仅提供getter,无setter。
|
||||||
|
* 支持通过 {@link #fromFlatMap(Map)} 静态工厂方法从扁平Map快速创建。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* Map<String,String> map = ...;
|
||||||
|
* Project project = Project.fromFlatMap(map);
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public final class Project {
|
||||||
|
|
||||||
|
/** 组织/分组名(如com.example) */
|
||||||
|
private final String group;
|
||||||
|
/** 构件/模块名(如app-core) */
|
||||||
|
private final String artifact;
|
||||||
|
/** 项目展示名称 */
|
||||||
|
private final String name;
|
||||||
|
/** 版本号(如1.0.0) */
|
||||||
|
private final String version;
|
||||||
|
/** 项目描述 */
|
||||||
|
private final String description;
|
||||||
|
/** 许可证标识 */
|
||||||
|
private final String license;
|
||||||
|
/** 项目主页URL */
|
||||||
|
private final String homepage;
|
||||||
|
|
||||||
|
/** 额外属性(不影响主字段,可用于模板/占位符) */
|
||||||
|
private final Map<String, String> properties;
|
||||||
|
/** 仓库列表(仓库ID -> 仓库对象) */
|
||||||
|
private final Map<String, Repository> repositories;
|
||||||
|
/** 依赖列表 */
|
||||||
|
private final List<Dependency> dependencies;
|
||||||
|
/** 构建配置 */
|
||||||
|
private final BuildConfiguration build;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造函数(私有),请使用 {@link #fromFlatMap(Map)} 创建实例。
|
||||||
|
*/
|
||||||
|
private Project(
|
||||||
|
String group,
|
||||||
|
String artifact,
|
||||||
|
String name,
|
||||||
|
String version,
|
||||||
|
String description,
|
||||||
|
String license,
|
||||||
|
String homepage,
|
||||||
|
Map<String, String> properties,
|
||||||
|
Map<String, Repository> repositories,
|
||||||
|
List<Dependency> dependencies,
|
||||||
|
BuildConfiguration build
|
||||||
|
) {
|
||||||
|
this.group = group;
|
||||||
|
this.artifact = artifact;
|
||||||
|
this.name = name;
|
||||||
|
this.version = version;
|
||||||
|
this.description = description;
|
||||||
|
this.license = license;
|
||||||
|
this.homepage = homepage;
|
||||||
|
this.properties = properties;
|
||||||
|
this.repositories = repositories;
|
||||||
|
this.dependencies = dependencies;
|
||||||
|
this.build = build;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过扁平Map创建 Project 实例。约定key格式如下:
|
||||||
|
* <ul>
|
||||||
|
* <li>project.* —— 项目元数据</li>
|
||||||
|
* <li>properties.* —— 额外属性</li>
|
||||||
|
* <li>repositories.* —— 仓库</li>
|
||||||
|
* <li>dependencies.* —— 依赖</li>
|
||||||
|
* <li>build.* —— 构建配置</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param map 扁平的配置map
|
||||||
|
* @return Project 实例
|
||||||
|
*/
|
||||||
|
public static Project fromFlatMap(Map<String, String> map) {
|
||||||
|
|
||||||
|
// 1. simple project metadata
|
||||||
|
String group = map.getOrDefault("project.group", "unknown");
|
||||||
|
String artifact = map.getOrDefault("project.artifact", "unknown");
|
||||||
|
String name = map.getOrDefault("project.name", artifact);
|
||||||
|
String version = map.getOrDefault("project.version", "0.0.1-SNAPSHOT");
|
||||||
|
String description = map.getOrDefault("project.description", "");
|
||||||
|
String license = map.getOrDefault("project.license", "");
|
||||||
|
String homepage = map.getOrDefault("project.homepage", "");
|
||||||
|
|
||||||
|
// 2. properties.*
|
||||||
|
Map<String, String> props = new LinkedHashMap<>();
|
||||||
|
map.forEach((k, v) -> {
|
||||||
|
if (k.startsWith("properties.")) {
|
||||||
|
props.put(k.substring("properties.".length()), v);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. repositories.*
|
||||||
|
Map<String, Repository> repos = new LinkedHashMap<>();
|
||||||
|
map.forEach((k, v) -> {
|
||||||
|
if (k.startsWith("repositories.")) {
|
||||||
|
String id = k.substring("repositories.".length());
|
||||||
|
repos.put(id, new Repository(id, v));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. dependencies.*
|
||||||
|
List<Dependency> deps = new ArrayList<>();
|
||||||
|
map.forEach((k, v) -> {
|
||||||
|
if (k.startsWith("dependencies.")) {
|
||||||
|
String id = k.substring("dependencies.".length());
|
||||||
|
deps.add(Dependency.fromString(id, v, props));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 5. build.* simply hand the subtree map
|
||||||
|
Map<String, String> buildMap = new LinkedHashMap<>();
|
||||||
|
map.forEach((k, v) -> {
|
||||||
|
if (k.startsWith("build.")) {
|
||||||
|
buildMap.put(k.substring("build.".length()), v);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
BuildConfiguration buildCfg = BuildConfiguration.fromFlatMap(buildMap, props);
|
||||||
|
|
||||||
|
return new Project(group, artifact, name, version, description, license, homepage, props, repos, deps, buildCfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return 组织/分组名 */
|
||||||
|
public String getGroup() {
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return 构件/模块名 */
|
||||||
|
public String getArtifact() {
|
||||||
|
return artifact;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return 项目名称 */
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return 版本号 */
|
||||||
|
public String getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return 项目描述 */
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return 许可证 */
|
||||||
|
public String getLicense() {
|
||||||
|
return license;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return 项目主页URL */
|
||||||
|
public String getHomepage() {
|
||||||
|
return homepage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return 额外属性映射 */
|
||||||
|
public Map<String, String> getProperties() {
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return 仓库映射 */
|
||||||
|
public Map<String, Repository> getRepositories() {
|
||||||
|
return repositories;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return 依赖列表 */
|
||||||
|
public List<Dependency> getDependencies() {
|
||||||
|
return dependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return 构建配置 */
|
||||||
|
public BuildConfiguration getBuild() {
|
||||||
|
return build;
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/main/java/org/jcnc/snow/pkg/model/Repository.java
Normal file
18
src/main/java/org/jcnc/snow/pkg/model/Repository.java
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package org.jcnc.snow.pkg.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表示一个远程仓库的基本信息,通常用于依赖解析和发布。
|
||||||
|
* <p>
|
||||||
|
* 每个仓库由唯一的ID和对应的URL确定。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* 示例:
|
||||||
|
* Repository repo = new Repository("central", "https://");
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param id 仓库唯一标识
|
||||||
|
* @param url 仓库地址(一般为HTTP(S)链接)
|
||||||
|
*/
|
||||||
|
public record Repository(String id, String url) {
|
||||||
|
}
|
||||||
@ -0,0 +1,82 @@
|
|||||||
|
package org.jcnc.snow.pkg.resolver;
|
||||||
|
|
||||||
|
import org.jcnc.snow.pkg.model.Dependency;
|
||||||
|
import org.jcnc.snow.pkg.model.Project;
|
||||||
|
import org.jcnc.snow.pkg.model.Repository;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 负责解析并下载项目依赖的工具类。
|
||||||
|
* <p>
|
||||||
|
* 支持本地缓存,如果依赖已存在于本地缓存则直接使用,否则尝试从项目仓库下载依赖包。
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
public final class DependencyResolver {
|
||||||
|
|
||||||
|
/** 本地缓存目录 */
|
||||||
|
private final Path localCache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 DependencyResolver 实例。
|
||||||
|
*
|
||||||
|
* @param localCacheDir 用于缓存依赖的本地目录
|
||||||
|
*/
|
||||||
|
public DependencyResolver(Path localCacheDir) {
|
||||||
|
this.localCache = localCacheDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析并下载指定项目的所有依赖。
|
||||||
|
* <p>
|
||||||
|
* 依赖优先从本地缓存读取,若未命中,则尝试从第一个配置的仓库下载。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param project 要解析依赖的项目
|
||||||
|
* @throws IOException 下载或文件操作失败时抛出
|
||||||
|
*/
|
||||||
|
public void resolve(Project project) throws IOException, URISyntaxException {
|
||||||
|
Files.createDirectories(localCache);
|
||||||
|
for (Dependency dep : project.getDependencies()) {
|
||||||
|
Path jarPath = localCache.resolve(dep.toPath());
|
||||||
|
if (Files.exists(jarPath)) {
|
||||||
|
System.out.println("[dependency] " + dep + " resolved from cache.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从第一个仓库下载
|
||||||
|
Optional<Repository> repo = project.getRepositories().values().stream().findFirst();
|
||||||
|
if (repo.isEmpty()) {
|
||||||
|
throw new IOException("No repository configured for dependency " + dep);
|
||||||
|
}
|
||||||
|
|
||||||
|
String url = repo.get().url() + "/" + dep.toPath();
|
||||||
|
download(url, jarPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从指定 URL 下载文件到本地目标路径。
|
||||||
|
*
|
||||||
|
* @param urlStr 远程文件 URL
|
||||||
|
* @param dest 本地目标路径
|
||||||
|
* @throws IOException 下载或保存文件时出错
|
||||||
|
*/
|
||||||
|
private void download(String urlStr, Path dest) throws IOException, URISyntaxException {
|
||||||
|
System.out.println("[download] " + urlStr);
|
||||||
|
Files.createDirectories(dest.getParent());
|
||||||
|
URL url = new URI(urlStr).toURL();
|
||||||
|
try (InputStream in = url.openStream()) {
|
||||||
|
Files.copy(in, dest, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
}
|
||||||
|
System.out.println("[saved] " + dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
48
src/main/java/org/jcnc/snow/pkg/tasks/CleanTask.java
Normal file
48
src/main/java/org/jcnc/snow/pkg/tasks/CleanTask.java
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package org.jcnc.snow.pkg.tasks;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理构建输出目录(如 build 和 dist)的任务实现。
|
||||||
|
* <p>
|
||||||
|
* 实现 {@link Task} 接口,通常用于构建流程中的清理阶段。
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
public final class CleanTask implements Task {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行清理任务,删除 "build" 和 "dist" 目录及其所有内容。
|
||||||
|
*
|
||||||
|
* @throws IOException 删除目录过程中出现 IO 错误时抛出
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void run() throws IOException {
|
||||||
|
deleteDir(Path.of("build"));
|
||||||
|
deleteDir(Path.of("dist"));
|
||||||
|
System.out.println("[clean] done.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 递归删除指定目录及其所有子文件和子目录。
|
||||||
|
* 使用 try-with-resources 自动关闭文件流,避免资源泄漏。
|
||||||
|
*
|
||||||
|
* @param dir 需要删除的目录路径
|
||||||
|
* @throws IOException 删除过程中出现 IO 错误时抛出
|
||||||
|
*/
|
||||||
|
private void deleteDir(Path dir) throws IOException {
|
||||||
|
if (Files.notExists(dir)) return;
|
||||||
|
try (var stream = Files.walk(dir)) {
|
||||||
|
stream.sorted(Comparator.reverseOrder()) // 先删子文件,后删父目录
|
||||||
|
.forEach(p -> {
|
||||||
|
try {
|
||||||
|
Files.delete(p);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/main/java/org/jcnc/snow/pkg/tasks/CompileTask.java
Normal file
43
src/main/java/org/jcnc/snow/pkg/tasks/CompileTask.java
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package org.jcnc.snow.pkg.tasks;
|
||||||
|
|
||||||
|
import org.jcnc.snow.pkg.model.Project;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编译项目源代码的任务实现。
|
||||||
|
* <p>
|
||||||
|
* 实现 {@link Task} 接口,用于构建流程中的编译阶段。当前仅为示例,未集成实际编译器。
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
public final class CompileTask implements Task {
|
||||||
|
|
||||||
|
/** 待编译的项目 */
|
||||||
|
private final Project project;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 CompileTask 实例。
|
||||||
|
*
|
||||||
|
* @param project 目标项目
|
||||||
|
*/
|
||||||
|
public CompileTask(Project project) {
|
||||||
|
this.project = project;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行编译任务,打印源代码目录和输出目录。
|
||||||
|
* <p>
|
||||||
|
* 实际编译尚未实现(TODO)。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @throws Exception 预留,未来集成编译器可能抛出异常
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void run() throws Exception {
|
||||||
|
// 获取源码目录和输出目录,默认分别为 "src" 和 "build/classes"
|
||||||
|
Path srcDir = Path.of(project.getProperties().getOrDefault("src_dir", "src"));
|
||||||
|
Path outDir = Path.of(project.getProperties().getOrDefault("output_dir", "build/classes"));
|
||||||
|
System.out.println("[compile] sources=" + srcDir + " output=" + outDir);
|
||||||
|
// TODO: 集成实际的编译器
|
||||||
|
}
|
||||||
|
}
|
||||||
67
src/main/java/org/jcnc/snow/pkg/tasks/PackageTask.java
Normal file
67
src/main/java/org/jcnc/snow/pkg/tasks/PackageTask.java
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package org.jcnc.snow.pkg.tasks;
|
||||||
|
|
||||||
|
import org.jcnc.snow.pkg.model.Project;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目打包任务,将编译输出目录(如 build/classes)打包为 .ice 文件。
|
||||||
|
* <p>
|
||||||
|
* 实现 {@link Task} 接口,通常用于构建流程中的打包阶段。
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
public final class PackageTask implements Task {
|
||||||
|
|
||||||
|
/** 目标项目 */
|
||||||
|
private final Project project;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 PackageTask 实例。
|
||||||
|
*
|
||||||
|
* @param project 目标项目
|
||||||
|
*/
|
||||||
|
public PackageTask(Project project) {
|
||||||
|
this.project = project;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行打包任务,将编译输出目录压缩为 artifact-version.ice 文件。
|
||||||
|
*
|
||||||
|
* @throws Exception 打包过程中出现 IO 或其他异常时抛出
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void run() throws Exception {
|
||||||
|
String artifact = project.getArtifact();
|
||||||
|
String version = project.getVersion();
|
||||||
|
String fileName = artifact + "-" + version + ".ice";
|
||||||
|
Path distDir = Path.of("dist");
|
||||||
|
Files.createDirectories(distDir);
|
||||||
|
Path packageFile = distDir.resolve(fileName);
|
||||||
|
|
||||||
|
try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(packageFile))) {
|
||||||
|
// 仅将编译输出目录打包
|
||||||
|
Path classesDir = Path.of("build/classes");
|
||||||
|
if (Files.exists(classesDir)) {
|
||||||
|
// 使用 try-with-resources 正确关闭 Stream<Path>
|
||||||
|
try (Stream<Path> stream = Files.walk(classesDir)) {
|
||||||
|
stream.filter(Files::isRegularFile)
|
||||||
|
.forEach(p -> {
|
||||||
|
try {
|
||||||
|
// 将文件以相对路径加入压缩包
|
||||||
|
zos.putNextEntry(new ZipEntry(classesDir.relativize(p).toString()));
|
||||||
|
Files.copy(p, zos);
|
||||||
|
zos.closeEntry();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println("[package] created " + packageFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
35
src/main/java/org/jcnc/snow/pkg/tasks/PublishTask.java
Normal file
35
src/main/java/org/jcnc/snow/pkg/tasks/PublishTask.java
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package org.jcnc.snow.pkg.tasks;
|
||||||
|
|
||||||
|
import org.jcnc.snow.pkg.model.Project;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发布项目构件到远程仓库的任务实现。
|
||||||
|
* <p>
|
||||||
|
* 实现 {@link Task} 接口,通常用于构建流程中的发布阶段。目前仅为演示,尚未实现实际上传。
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
public final class PublishTask implements Task {
|
||||||
|
|
||||||
|
/** 目标项目 */
|
||||||
|
private final Project project;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 PublishTask 实例。
|
||||||
|
*
|
||||||
|
* @param project 目标项目
|
||||||
|
*/
|
||||||
|
public PublishTask(Project project) {
|
||||||
|
this.project = project;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行发布任务。当前仅打印发布提示,未实现实际上传逻辑。
|
||||||
|
*
|
||||||
|
* @throws Exception 预留,未来实现上传逻辑时可能抛出异常
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void run() throws Exception {
|
||||||
|
// TODO: 实现上传到仓库(如 HTTP PUT/POST)
|
||||||
|
System.out.println("[publish] uploading artifact " + project.getArtifact() + "-" + project.getVersion());
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/main/java/org/jcnc/snow/pkg/tasks/Task.java
Normal file
17
src/main/java/org/jcnc/snow/pkg/tasks/Task.java
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package org.jcnc.snow.pkg.tasks;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建任务的通用接口,所有具体任务都应实现该接口。
|
||||||
|
* <p>
|
||||||
|
* 用于统一生命周期内的任务行为,例如编译、打包、清理等。
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
public interface Task {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行具体任务的入口方法。
|
||||||
|
*
|
||||||
|
* @throws Exception 任务执行过程中出现的异常
|
||||||
|
*/
|
||||||
|
void run() throws Exception;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user