import com.github.jk1.license.render.* import com.github.jk1.license.filter.ExcludeTransitiveDependenciesFilter import com.github.jk1.license.filter.LicenseBundleNormalizer import org.commonmark.parser.Parser import org.commonmark.renderer.html.HtmlRenderer import com.github.jk1.license.render.InventoryMarkdownReportRenderer import org.jsoup.Jsoup import org.springframework.boot.gradle.tasks.bundling.BootJar //import org.gradle.internal.impldep.org.jsoup.Jsoup buildscript { repositories { mavenCentral() } dependencies { classpath ("org.jsoup:jsoup:1.18.1") // 빌드 스크립트에서 commonmark 라이브러리를 사용할 수 있도록 추가합니다. classpath("org.commonmark:commonmark:0.18.0") } } plugins { kotlin("jvm") version "1.9.25" kotlin("plugin.spring") version "1.9.25" id("org.springframework.boot") version "3.3.4" id("io.spring.dependency-management") version "1.1.6" id("com.github.jk1.dependency-license-report") version "2.0" } group = "kr.lunaticbum.back" version = "0.0.7-SNAPSHOT" java { toolchain { languageVersion = JavaLanguageVersion.of(17) } } configurations { compileOnly { extendsFrom(configurations.annotationProcessor.get()) } } repositories { mavenCentral() maven { url = uri("https://repo.spring.io/milestone") } } dependencies { // implementation ("jakarta.servlet:jakarta.servlet-api") //스프링부트 3.0 이상 // implementation ("jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api") //스프링부트 3.0 이상 // implementation ("org.glassfish.web:jakarta.servlet.jsp.jstl") //스프링부트 3.0 이상 implementation ("org.slf4j:jcl-over-slf4j") // implementation ("org.springframework.boot:spring-boot-starter-batch") implementation ("org.springframework.boot:spring-boot-starter-quartz") implementation ("com.google.code.gson:gson:2.11.0") implementation ("org.apache.tomcat.embed:tomcat-embed-jasper") implementation("org.springframework.boot:spring-boot-starter-data-mongodb-reactive") implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-webflux") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("io.projectreactor.kotlin:reactor-kotlin-extensions") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor") implementation("org.springframework.boot:spring-boot-starter-thymeleaf") implementation("org.thymeleaf.extras:thymeleaf-extras-springsecurity6") implementation("nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect") implementation ("org.jsoup:jsoup:1.18.1") implementation ("org.seleniumhq.selenium:selenium-java:4.10.0") implementation ("org.commonmark:commonmark:0.18.0") implementation ("net.coobird:thumbnailator:0.4.14") implementation("org.sejda.imageio:webp-imageio:0.1.6") implementation ("com.drewnoakes:metadata-extractor:2.19.0") implementation("org.springframework.boot:spring-boot-starter-security") compileOnly("org.projectlombok:lombok") // implementation(platform("com.google.cloud:libraries-bom: 26.55.0")) // implementation("com.google.cloud:google-cloud-apikeys") implementation ("com.google.maps:google-maps-services:2.2.0") // implementation ("org.springframework.ai:spring-ai-openai-spring-boot-starter:1.0.0-SNAPSHOT") // implementation ("org.springframework.ai:spring-ai-vertex-ai-gemini-spring-boot-starter:1.0.0-SNAPSHOT") // implementation("org.springframework.ai:spring-ai-ollama-spring-boot-starter:1.0.0-SNAPSHOT") implementation(platform("org.springframework.ai:spring-ai-bom:1.0.0-M6")) implementation("org.springframework.ai:spring-ai-ollama-spring-boot-starter:1.0.0-M6") implementation ("org.springframework.ai:spring-ai-qdrant-store-spring-boot-starter") // implementation ("io.qdrant:client:1.13.0") implementation ("org.slf4j:slf4j-simple:1.7.25") implementation("io.jsonwebtoken:jjwt-api:0.11.5") implementation("io.jsonwebtoken:jjwt-impl:0.11.5") implementation("io.jsonwebtoken:jjwt-jackson:0.11.5") annotationProcessor("org.projectlombok:lombok") testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("io.projectreactor:reactor-test") testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") testRuntimeOnly("org.junit.platform:junit-platform-launcher") // JSON 처리를 위한 Gson 라이브러리 implementation("com.google.code.gson:gson:2.10.1") } kotlin { compilerOptions { freeCompilerArgs.addAll("-Xjsr305=strict") } } tasks.withType() { duplicatesStrategy = DuplicatesStrategy.EXCLUDE exclude("META-INF/BC1024KE.RSA", "META-INF/BC1024KE.SF", "META-INF/BC1024KE.DSA") exclude("META-INF/BC2048KE.RSA", "META-INF/BC2048KE.SF", "META-INF/BC2048KE.DSA") } tasks.withType { useJUnitPlatform() } tasks.jar { archiveFileName.set("app.jar") manifest { attributes["Main-Class"] = "kr.lunaticbum.back.lun.LunApplicationKt" } configurations["compileClasspath"].forEach { file: File -> from(zipTree(file.absoluteFile)) } duplicatesStrategy = DuplicatesStrategy.EXCLUDE } tasks.jar { // Otherwise you'll get a "No main manifest attribute" error manifest { attributes["Main-Class"] = "kr.lunaticbum.back.lun.LunApplicationKt" } // To avoid the duplicate handling strategy error duplicatesStrategy = DuplicatesStrategy.EXCLUDE // To add all of the dependencies otherwise a "NoClassDefFoundError" error from(sourceSets.main.get().output) dependsOn(configurations.runtimeClasspath) from({ configurations.runtimeClasspath.get().filter { it.name.endsWith("jar") }.map { zipTree(it) } }) } // ✅ licenseReport는 이전과 동일하게 Markdown을 생성하도록 둡니다. licenseReport { outputDir = "$projectDir/build/licenses" renderers = arrayOf(InventoryMarkdownReportRenderer()) // filters = arrayOf(com.github.jk1.license.filter.LicenseBundleNormalizer(), com.github.jk1.license.filter.ExcludeTransitiveDependenciesFilter()) } tasks.register("updateLicensePage") { dependsOn("generateLicenseReport") doLast { // file("$projectDir/build/licenses").listFiles().forEach { // println("${it.absolutePath}: ${it.name}") // } // ... 로그 출력 로직은 그대로 유지 ... println("🚀 'updateLicensePage' 태스크를 시작합니다.") val licenseMarkdownFile = file("$projectDir/build/licenses/licenses.md") val targetHtmlFile = file("src/main/resources/templates/content/licenses.html") println(" - 원본 마크다운 파일: ${licenseMarkdownFile.path}") println(" - 대상 HTML 파일: ${targetHtmlFile.path}") if (!licenseMarkdownFile.exists()) { throw GradleException("❌ 라이선스 마크다운 파일이 생성되지 않았습니다. '${licenseMarkdownFile.path}'") } val licenseMarkdown = licenseMarkdownFile.readText() println(" - 마크다운 파일을 성공적으로 읽었습니다. (내용 길이: ${licenseMarkdown.length})") val parser = Parser.builder().build() val renderer = HtmlRenderer.builder().build() val licenseHtml = renderer.render(parser.parse(licenseMarkdown)) println(" - 마크다운을 HTML로 변환했습니다. (HTML 길이: ${licenseHtml.length})") // ✅ Jsoup으로 HTML 파일을 파싱합니다. val doc = Jsoup.parse(targetHtmlFile, "UTF-8") // ✅ CSS 선택자를 이용해 ID가 'license-content-container'인 태그를 선택하고 // 그 내부 HTML을 생성된 라이선스 내용으로 교체합니다. doc.selectFirst("#license-content-container")?.html(licenseHtml) println(" - HTML 파일 내 placeholder div의 내용을 교체했습니다.") // ✅ 변경된 HTML 내용을 파일에 다시 씁니다. targetHtmlFile.writeText(doc.outerHtml()) println("✅ 라이선스 정보(HTML)가 '${targetHtmlFile.name}' 파일에 성공적으로 업데이트되었습니다.") } } // 'build' 태스크 실행 시 이 작업이 자동으로 수행되도록 연결 // [수정 전] tasks.build { dependsOn(tasks.getByName("updateLicensePage")) } tasks.named("build") { // [수정 후] 'build' 태스크를 더 안전하게 참조합니다. dependsOn(tasks.named("updateLicensePage")) } tasks.named("bootJar") { // [수정 후] 'build' 태스크를 더 안전하게 참조합니다. dependsOn(tasks.named("updateLicensePage")) } // 기본 bootJar 태스크의 설정을 가져오기 위한 참조 val bootJar by tasks.getting(BootJar::class) // //// 'prod' 프로필이 내장된 JAR를 빌드하는 최종 태스크 정의 //tasks.register("bootJarProd") { // group = "build" // description = "Builds a production JAR that defaults to the 'prod' profile." // archiveClassifier.set("prod") // // // --- 필수 설정 복사 --- // // 1. Main 클래스 설정 복사 // mainClass.set(bootJar.mainClass) // // 2. Classpath 설정 복사 // classpath = bootJar.classpath // // 3. Target Java Version 설정 복사 (이번 오류 해결) // targetJavaVersion.set(bootJar.targetJavaVersion) // // manifest { // attributes["Spring-Profiles-Active"] = "prod" // } //} // "local" 프로파일용 JAR를 빌드하는 작업 tasks.register("bootJarLocal") { group = "build" description = "로컬 환경용 JAR 파일을 빌드합니다 ('local' 프로파일 적용)." archiveClassifier.set("local") // 파일 이름에 local 접미사 추가 (e.g., app-local.jar) // 메인 클래스와 클래스패스는 기본 bootJar 설정을 따라갑니다. mainClass.set(tasks.bootJar.get().mainClass) classpath = tasks.bootJar.get().classpath targetJavaVersion.set(bootJar.targetJavaVersion) // 'resources' 폴더의 모든 파일을 복사하되... from("src/main/resources") { include("**/*") // prod 설정 파일은 제외합니다. exclude("application-prod.properties") // local 설정 파일의 이름을 application.properties로 변경합니다. rename("application-local.properties", "application.properties") } } // "prod" 프로파일용 JAR를 빌드하는 작업 tasks.register("bootJarProd") { group = "build" description = "운영 환경용 JAR 파일을 빌드합니다 ('prod' 프로파일 적용)." archiveClassifier.set("prod") // 파일 이름에 prod 접미사 추가 (e.g., app-prod.jar) // 메인 클래스와 클래스패스는 기본 bootJar 설정을 따라갑니다. mainClass.set(tasks.bootJar.get().mainClass) classpath = tasks.bootJar.get().classpath targetJavaVersion.set(bootJar.targetJavaVersion) // 'resources' 폴더의 모든 파일을 복사하되... from("src/main/resources") { include("**/*") // local 설정 파일은 제외합니다. exclude("application-local.properties") // prod 설정 파일의 이름을 application.properties로 변경합니다. rename("application-prod.properties", "application.properties") } } // 🚀 1. 명령어를 실행할 새로운 Exec 태스크 정의 tasks.register("runCommandAfterProdJar") { group = "build" description = "prod JAR 빌드 후 실행할 명령어를 정의합니다." // 이 태스크는 bootJarProd가 성공해야만 의미가 있으므로, 의존성을 명시해주는 것이 좋습니다. dependsOn(tasks.named("bootJarProd")) // 실행할 OS 명령어와 인자를 설정합니다. // 예시 1: Docker 이미지 빌드 commandLine("docker", "buildx","buildx","--platform","linux/amd64", "-t", "lunaticbum/testjar:0.025", ".") // 예시 2: 빌드된 JAR 파일을 특정 서버로 복사 // commandLine("scp", "build/libs/your-app-name-prod.jar", "user@server:/path/to/deploy") // 예시 3: 간단한 셸 스크립트 실행 // commandLine("./deploy.sh") // 필요하다면 작업 디렉토리를 설정할 수 있습니다. // workingDir = rootDir // doLast { // println("prod JAR 빌드가 완료되었습니다. 추가 명령어를 실행합니다.") // exec { // commandLine("docker", "push", "lunaticbum/testjar:0.025") // // commandLine("echo", "Hello from doLast!") // } // } } // 🚀 2. bootJarProd 태스크가 끝나면 위에서 정의한 태스크를 실행하도록 연결 //tasks.named("bootJarProd") { // finalizedBy(tasks.named("runCommandAfterProdJar")) //} // //// 'build' 태스크 실행 시 이 작업이 자동으로 수행되도록 연결 //tasks.build { // dependsOn(tasks.getByName("updateLicensePage")) //}