-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Expand file tree
/
Copy pathjacoco_agent.go
More file actions
174 lines (142 loc) · 5.22 KB
/
jacoco_agent.go
File metadata and controls
174 lines (142 loc) · 5.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
package frameworks
import (
"fmt"
"github.com/cloudfoundry/java-buildpack/src/java/common"
"path/filepath"
"github.com/cloudfoundry/libbuildpack"
)
// JacocoAgentFramework implements JaCoCo code coverage agent support
type JacocoAgentFramework struct {
context *common.Context
}
// NewJacocoAgentFramework creates a new JaCoCo agent framework instance
func NewJacocoAgentFramework(ctx *common.Context) *JacocoAgentFramework {
return &JacocoAgentFramework{context: ctx}
}
// Detect checks if JaCoCo agent should be included
func (j *JacocoAgentFramework) Detect() (string, error) {
// Check for JaCoCo service binding
vcapServices, err := GetVCAPServices()
if err != nil {
j.context.Log.Warning("Failed to parse VCAP_SERVICES: %s", err.Error())
return "", nil
}
// JaCoCo can be bound as:
// - Services with "jacoco" in the label
// - Services with "jacoco" tag
// - User-provided services with "jacoco" in the name
// Must have "address" credential
if vcapServices.HasService("jacoco") || vcapServices.HasTag("jacoco") || vcapServices.HasServiceByNamePattern("jacoco") {
service := vcapServices.GetService("jacoco")
if service == nil {
service = vcapServices.GetServiceByNamePattern("jacoco")
}
// Verify "address" credential exists (required for JaCoCo)
if service != nil {
if _, hasAddress := service.Credentials["address"]; hasAddress {
j.context.Log.Info("JaCoCo service detected with address!")
return "JaCoCo Agent", nil
}
}
}
j.context.Log.Debug("JaCoCo not detected")
return "", nil
}
// Supply installs the JaCoCo agent
func (j *JacocoAgentFramework) Supply() error {
j.context.Log.BeginStep("Installing JaCoCo Agent")
// Get JaCoCo agent dependency from manifest
dep, err := j.context.Manifest.DefaultVersion("jacoco")
if err != nil {
j.context.Log.Warning("Unable to determine JaCoCo version, using default")
dep = libbuildpack.Dependency{
Name: "jacoco",
Version: "0.8.12", // Fallback version
}
}
// Install JaCoCo agent ZIP
agentDir := filepath.Join(j.context.Stager.DepDir(), "jacoco_agent")
if err := j.context.Installer.InstallDependency(dep, agentDir); err != nil {
return fmt.Errorf("failed to install JaCoCo agent: %w", err)
}
j.context.Log.Info("Installed JaCoCo Agent version %s", dep.Version)
return nil
}
// findJacocoAgent locates the jacocoagent.jar file in the installation directory
func (j *JacocoAgentFramework) findJacocoAgent(installDir string) (string, error) {
return FindFileInDirectory(installDir, "jacocoagent.jar", []string{"", "lib"})
}
// Finalize configures the JaCoCo agent for runtime
func (j *JacocoAgentFramework) Finalize() error {
// Get JaCoCo service credentials
vcapServices, err := GetVCAPServices()
if err != nil {
return fmt.Errorf("failed to parse VCAP_SERVICES: %w", err)
}
service := vcapServices.GetService("jacoco")
if service == nil {
service = vcapServices.GetServiceByNamePattern("jacoco")
}
if service == nil {
return fmt.Errorf("JaCoCo service binding not found")
}
credentials := service.Credentials
// Build javaagent properties
properties := make(map[string]string)
// Required properties
if address, ok := credentials["address"].(string); ok {
properties["address"] = address
} else {
return fmt.Errorf("JaCoCo service binding missing required 'address' credential")
}
// Default output mode
properties["output"] = "tcpclient"
// Session ID based on CF instance GUID
properties["sessionid"] = "$CF_INSTANCE_GUID"
// Optional properties from service credentials
if excludes, ok := credentials["excludes"].(string); ok && excludes != "" {
properties["excludes"] = excludes
}
if includes, ok := credentials["includes"].(string); ok && includes != "" {
properties["includes"] = includes
}
if port, ok := credentials["port"].(string); ok && port != "" {
properties["port"] = port
}
if output, ok := credentials["output"].(string); ok && output != "" {
properties["output"] = output
}
// Get buildpack index for multi-buildpack support
depsIdx := j.context.Stager.DepsIdx()
// Find jacocoagent.jar at staging time to determine relative path
agentDir := filepath.Join(j.context.Stager.DepDir(), "jacoco_agent")
agentJar, err := j.findJacocoAgent(agentDir)
if err != nil {
return fmt.Errorf("failed to locate jacocoagent.jar: %w", err)
}
j.context.Log.Debug("Found JaCoCo agent at: %s", agentJar)
// Build runtime path using $DEPS_DIR
relPath, err := filepath.Rel(j.context.Stager.DepDir(), agentJar)
if err != nil {
return fmt.Errorf("failed to compute relative path: %w", err)
}
runtimeAgentPath := filepath.Join(fmt.Sprintf("$DEPS_DIR/%s", depsIdx), relPath)
// Build javaagent option with runtime path
javaagentOpts := fmt.Sprintf("-javaagent:%s", runtimeAgentPath)
// Append properties as key=value pairs separated by commas
first := true
for key, value := range properties {
if first {
javaagentOpts += fmt.Sprintf("=%s=%s", key, value)
first = false
} else {
javaagentOpts += fmt.Sprintf(",%s=%s", key, value)
}
}
// Write to .opts file using priority 26
if err := writeJavaOptsFile(j.context, 26, "jacoco", javaagentOpts); err != nil {
return fmt.Errorf("failed to write java_opts file: %w", err)
}
j.context.Log.Info("JaCoCo Agent configured (priority 26)")
return nil
}