diff --git a/src/java/containers/container.go b/src/java/containers/container.go index 6a5b0a1c2..8feed5dd5 100644 --- a/src/java/containers/container.go +++ b/src/java/containers/container.go @@ -107,3 +107,20 @@ func (r *Registry) RegisterStandardContainers() { r.Register(NewDistZipContainer(r.context)) r.Register(NewJavaMainContainer(r.context)) } + +var symlinkScript = `#!/bin/bash +set -uo pipefail +TARGET_DIR="$PWD/%s" +CLASSPATH=${CLASSPATH:-} +mkdir -p "$TARGET_DIR" +# Split CLASSPATH on : +IFS=':' read -ra PATHS <<< "$CLASSPATH" +for p in "${PATHS[@]}"; do + # Skip empty entries + [[ -z "$p" ]] && continue + name=$(basename "$p") + link="$TARGET_DIR/$name" + ln -s "$p" "$link" + echo "Created symlink: $link -> $p" +done +` diff --git a/src/java/containers/spring_boot.go b/src/java/containers/spring_boot.go index cabc4d4ef..303992e1d 100644 --- a/src/java/containers/spring_boot.go +++ b/src/java/containers/spring_boot.go @@ -1,8 +1,8 @@ package containers import ( - "github.com/cloudfoundry/java-buildpack/src/java/common" "fmt" + "github.com/cloudfoundry/java-buildpack/src/java/common" "os" "path/filepath" "strings" @@ -234,6 +234,12 @@ func (s *SpringBootContainer) Release() (string, error) { bootInf := filepath.Join(buildDir, "BOOT-INF") if _, err := os.Stat(bootInf); err == nil { // Verify this is actually a Spring Boot application + + // the script name is prefixed with 'zzz' as it is important to be the last script sourced from profile.d + // so that the previous scripts assembling the CLASSPATH variable(left from frameworks) are sourced previous to it. + if err := s.context.Stager.WriteProfileD("zzz_classpath_symlinks.sh", fmt.Sprintf(symlinkScript, filepath.Join("BOOT-INF", "lib"))); err != nil { + return "", fmt.Errorf("failed to write zzz_classpath_symlinks.sh: %w", err) + } if s.isSpringBootExplodedJar(buildDir) { // True Spring Boot exploded JAR - use JarLauncher // Determine the correct JarLauncher class name based on Spring Boot version diff --git a/src/java/containers/tomcat.go b/src/java/containers/tomcat.go index be53ad148..f264133ba 100644 --- a/src/java/containers/tomcat.go +++ b/src/java/containers/tomcat.go @@ -604,6 +604,12 @@ func (t *TomcatContainer) Finalize() error { webInf := filepath.Join(buildDir, "WEB-INF") if _, err := os.Stat(webInf); err == nil { + // the script name is prefixed with 'zzz' as it is important to be the last script sourced from profile.d + // so that the previous scripts assembling the CLASSPATH variable(left from frameworks) are sourced previous to it. + if err := t.context.Stager.WriteProfileD("zzz_classpath_symlinks.sh", fmt.Sprintf(symlinkScript, filepath.Join("WEB-INF", "lib"))); err != nil { + return fmt.Errorf("failed to write zzz_classpath_symlinks.sh: %w", err) + } + contextXMLDir := filepath.Dir(contextXMLPath) if err := os.MkdirAll(contextXMLDir, 0755); err != nil { return fmt.Errorf("failed to create context directory: %w", err) diff --git a/src/java/finalize/finalize.go b/src/java/finalize/finalize.go index 665b41344..55802df07 100644 --- a/src/java/finalize/finalize.go +++ b/src/java/finalize/finalize.go @@ -102,7 +102,7 @@ func Run(f *Finalizer) error { } // Finalize frameworks (APM agents, etc.) - if err := f.finalizeFrameworks(); err != nil { + if err := f.finalizeFrameworks(ctx); err != nil { f.Log.Error("Failed to finalize frameworks: %s", err.Error()) return err } @@ -158,17 +158,9 @@ func (f *Finalizer) finalizeJRE() error { } // finalizeFrameworks finalizes framework components (APM agents, etc.) -func (f *Finalizer) finalizeFrameworks() error { +func (f *Finalizer) finalizeFrameworks(ctx *common.Context) error { f.Log.BeginStep("Finalizing frameworks") - ctx := &common.Context{ - Stager: f.Stager, - Manifest: f.Manifest, - Installer: f.Installer, - Log: f.Log, - Command: f.Command, - } - registry := frameworks.NewRegistry(ctx) registry.RegisterStandardFrameworks() diff --git a/src/java/frameworks/client_certificate_mapper.go b/src/java/frameworks/client_certificate_mapper.go index 879e2819a..f0fae5ed4 100644 --- a/src/java/frameworks/client_certificate_mapper.go +++ b/src/java/frameworks/client_certificate_mapper.go @@ -69,17 +69,17 @@ func (c *ClientCertificateMapperFramework) Finalize() error { return nil } - // Add to classpath via CLASSPATH environment variable - classpath := os.Getenv("CLASSPATH") - if classpath != "" { - classpath += ":" - } - classpath += matches[0] + depsIdx := c.context.Stager.DepsIdx() + runtimePath := fmt.Sprintf("$DEPS_DIR/%s/client_certificate_mapper/%s", depsIdx, filepath.Base(matches[0])) + + profileScript := fmt.Sprintf("export CLASSPATH=\"%s${CLASSPATH:+:$CLASSPATH}\"\n", runtimePath) - if err := c.context.Stager.WriteEnvFile("CLASSPATH", classpath); err != nil { - return fmt.Errorf("failed to set CLASSPATH for Client Certificate Mapper: %w", err) + if err := c.context.Stager.WriteProfileD("client_certificate_mapper.sh", profileScript); err != nil { + return fmt.Errorf("failed to write client_certificate_mapper.sh profile.d script: %w", err) } - + + c.context.Log.Debug("Client Certificate Mapper JAR will be added to classpath at runtime: %s", runtimePath) + return nil } diff --git a/src/java/frameworks/java_cf_env.go b/src/java/frameworks/java_cf_env.go index 3924342b9..f1a0294ef 100644 --- a/src/java/frameworks/java_cf_env.go +++ b/src/java/frameworks/java_cf_env.go @@ -1,8 +1,8 @@ package frameworks import ( - "github.com/cloudfoundry/java-buildpack/src/java/common" "fmt" + "github.com/cloudfoundry/java-buildpack/src/java/common" "os" "path/filepath" "strings" @@ -81,17 +81,16 @@ func (j *JavaCfEnvFramework) Finalize() error { return nil } - // Add to classpath via CLASSPATH environment variable - classpath := os.Getenv("CLASSPATH") - if classpath != "" { - classpath += ":" - } - classpath += matches[0] + depsIdx := j.context.Stager.DepsIdx() + runtimePath := fmt.Sprintf("$DEPS_DIR/%s/java_cf_env/%s", depsIdx, filepath.Base(matches[0])) - if err := j.context.Stager.WriteEnvFile("CLASSPATH", classpath); err != nil { - return fmt.Errorf("failed to set CLASSPATH for Java CF Env: %w", err) + profileScript := fmt.Sprintf("export CLASSPATH=\"%s${CLASSPATH:+:$CLASSPATH}\"\n", runtimePath) + if err := j.context.Stager.WriteProfileD("java_cf_env.sh", profileScript); err != nil { + return fmt.Errorf("failed to write java_cf_env.sh profile.d script: %w", err) } + j.context.Log.Debug("Java CF Env JAR will be added to classpath at runtime: %s", runtimePath) + return nil } diff --git a/src/java/frameworks/maria_db_jdbc.go b/src/java/frameworks/maria_db_jdbc.go index 3f8010e74..da85c65f1 100644 --- a/src/java/frameworks/maria_db_jdbc.go +++ b/src/java/frameworks/maria_db_jdbc.go @@ -88,8 +88,11 @@ func (f *MariaDBJDBCFramework) Finalize() error { f.context.Log.BeginStep("Configuring MariaDB JDBC driver") - // Add to CLASSPATH environment variable - if err := f.context.Stager.WriteEnvFile("CLASSPATH", f.jarPath); err != nil { + depsIdx := f.context.Stager.DepsIdx() + runtimePath := fmt.Sprintf("$DEPS_DIR/%s/mariadb_jdbc/%s", depsIdx, filepath.Base(f.jarPath)) + + profileScript := fmt.Sprintf("export CLASSPATH=\"%s${CLASSPATH:+:$CLASSPATH}\"\n", runtimePath) + if err := f.context.Stager.WriteProfileD("mariadb_jdbc.sh", profileScript); err != nil { f.context.Log.Warning("Failed to add MariaDB JDBC to CLASSPATH: %s", err) return nil // Non-blocking } diff --git a/src/java/frameworks/postgresql_jdbc.go b/src/java/frameworks/postgresql_jdbc.go index f8f99605b..8d9310a4a 100644 --- a/src/java/frameworks/postgresql_jdbc.go +++ b/src/java/frameworks/postgresql_jdbc.go @@ -3,7 +3,6 @@ package frameworks import ( "fmt" "github.com/cloudfoundry/java-buildpack/src/java/common" - "os" "path/filepath" "strings" @@ -73,17 +72,16 @@ func (p *PostgresqlJdbcFramework) Finalize() error { return nil } - // Add to classpath via CLASSPATH environment variable - classpath := os.Getenv("CLASSPATH") - if classpath != "" { - classpath += ":" - } - classpath += matches[0] + depsIdx := p.context.Stager.DepsIdx() + runtimePath := fmt.Sprintf("$DEPS_DIR/%s/postgresql_jdbc/%s", depsIdx, filepath.Base(matches[0])) - if err := p.context.Stager.WriteEnvFile("CLASSPATH", classpath); err != nil { - return fmt.Errorf("failed to set CLASSPATH for PostgreSQL JDBC: %w", err) + profileScript := fmt.Sprintf("export CLASSPATH=\"%s${CLASSPATH:+:$CLASSPATH}\"\n", runtimePath) + if err := p.context.Stager.WriteProfileD("postgresql_jdbc.sh", profileScript); err != nil { + return fmt.Errorf("failed to write postgresql_jdbc.sh profile.d script: %w", err) } + p.context.Log.Debug("PostgreSQL JDBC JAR will be added to classpath at runtime: %s", runtimePath) + return nil } diff --git a/src/java/frameworks/spring_auto_reconfiguration.go b/src/java/frameworks/spring_auto_reconfiguration.go index 897ab8a1f..8d4d66885 100644 --- a/src/java/frameworks/spring_auto_reconfiguration.go +++ b/src/java/frameworks/spring_auto_reconfiguration.go @@ -101,17 +101,16 @@ func (s *SpringAutoReconfigurationFramework) Finalize() error { return nil } - // Add to classpath via CLASSPATH environment variable - classpath := os.Getenv("CLASSPATH") - if classpath != "" { - classpath += ":" - } - classpath += matches[0] + depsIdx := s.context.Stager.DepsIdx() + runtimePath := fmt.Sprintf("$DEPS_DIR/%s/spring_auto_reconfiguration/%s", depsIdx, filepath.Base(matches[0])) - if err := s.context.Stager.WriteEnvFile("CLASSPATH", classpath); err != nil { - return fmt.Errorf("failed to set CLASSPATH for Spring Auto-reconfiguration: %w", err) + profileScript := fmt.Sprintf("export CLASSPATH=\"%s${CLASSPATH:+:$CLASSPATH}\"\n", runtimePath) + if err := s.context.Stager.WriteProfileD("spring_auto_reconfiguration.sh", profileScript); err != nil { + return fmt.Errorf("failed to write spring_auto_reconfiguration.sh profile.d script: %w", err) } + s.context.Log.Debug("Spring Auto-reconfiguration JAR will be added to classpath at runtime: %s", runtimePath) + return nil }