Skip to content

Structured Editing ​

Gradle Parser provides powerful structured editing capabilities that allow you to modify Gradle build files programmatically while preserving formatting and minimizing diffs.

Quick Edits ​

Update Dependency Version ​

The simplest way to update a dependency version:

go
import "github.com/scagogogo/gradle-parser/pkg/api"

// Update MySQL connector version
newContent, err := api.UpdateDependencyVersion(
    "build.gradle",
    "mysql",
    "mysql-connector-java", 
    "8.0.31"
)
if err != nil {
    log.Fatal(err)
}

// Write back to file
err = os.WriteFile("build.gradle", []byte(newContent), 0644)
if err != nil {
    log.Fatal(err)
}

Update Plugin Version ​

Update a plugin version:

go
// Update Spring Boot plugin
newContent, err := api.UpdatePluginVersion(
    "build.gradle",
    "org.springframework.boot",
    "2.7.2"
)
if err != nil {
    log.Fatal(err)
}

// Save the changes
err = os.WriteFile("build.gradle", []byte(newContent), 0644)
if err != nil {
    log.Fatal(err)
}

Advanced Editing with GradleEditor ​

For more complex modifications, use the GradleEditor:

Creating an Editor ​

go
editor, err := api.CreateGradleEditor("build.gradle")
if err != nil {
    log.Fatal(err)
}

Multiple Modifications ​

Perform multiple edits in a single operation:

go
editor, err := api.CreateGradleEditor("build.gradle")
if err != nil {
    log.Fatal(err)
}

// Update multiple dependencies
err = editor.UpdateDependencyVersion("mysql", "mysql-connector-java", "8.0.31")
if err != nil {
    log.Printf("Failed to update MySQL: %v", err)
}

err = editor.UpdateDependencyVersion("org.springframework", "spring-core", "5.3.21")
if err != nil {
    log.Printf("Failed to update Spring: %v", err)
}

// Update plugin version
err = editor.UpdatePluginVersion("org.springframework.boot", "2.7.2")
if err != nil {
    log.Printf("Failed to update Spring Boot plugin: %v", err)
}

// Update project properties
err = editor.UpdateProperty("version", "1.0.0")
if err != nil {
    log.Printf("Failed to update version: %v", err)
}

// Apply all changes
newContent, err := editor.ApplyModifications()
if err != nil {
    log.Fatal(err)
}

// Save to file
err = os.WriteFile("build.gradle", []byte(newContent), 0644)
if err != nil {
    log.Fatal(err)
}

Adding Dependencies ​

Add new dependencies to the project:

go
editor, err := api.CreateGradleEditor("build.gradle")
if err != nil {
    log.Fatal(err)
}

// Add a new implementation dependency
err = editor.AddDependency(
    "org.apache.commons",
    "commons-text",
    "1.9",
    "implementation"
)
if err != nil {
    log.Fatal(err)
}

// Add a test dependency
err = editor.AddDependency(
    "org.junit.jupiter",
    "junit-jupiter-api",
    "5.8.2",
    "testImplementation"
)
if err != nil {
    log.Fatal(err)
}

// Apply changes
newContent, err := editor.ApplyModifications()
if err != nil {
    log.Fatal(err)
}

Batch Operations ​

Bulk Dependency Updates ​

Update multiple dependencies efficiently:

go
func bulkUpdateDependencies(buildFile string, updates map[string]string) error {
    editor, err := api.CreateGradleEditor(buildFile)
    if err != nil {
        return err
    }

    // Apply all updates
    for depKey, newVersion := range updates {
        parts := strings.Split(depKey, ":")
        if len(parts) != 2 {
            log.Printf("Invalid dependency key: %s", depKey)
            continue
        }
        
        group, name := parts[0], parts[1]
        err = editor.UpdateDependencyVersion(group, name, newVersion)
        if err != nil {
            log.Printf("Failed to update %s: %v", depKey, err)
        }
    }

    // Apply all modifications
    newContent, err := editor.ApplyModifications()
    if err != nil {
        return err
    }

    // Write back to file
    return os.WriteFile(buildFile, []byte(newContent), 0644)
}

// Usage
updates := map[string]string{
    "mysql:mysql-connector-java":           "8.0.31",
    "org.springframework:spring-core":      "5.3.21",
    "com.google.guava:guava":              "31.1-jre",
    "org.apache.commons:commons-lang3":    "3.12.0",
}

err := bulkUpdateDependencies("build.gradle", updates)
if err != nil {
    log.Fatal(err)
}

Plugin Management ​

go
func updateAllPlugins(buildFile string, pluginVersions map[string]string) error {
    editor, err := api.CreateGradleEditor(buildFile)
    if err != nil {
        return err
    }

    for pluginId, version := range pluginVersions {
        err = editor.UpdatePluginVersion(pluginId, version)
        if err != nil {
            log.Printf("Failed to update plugin %s: %v", pluginId, err)
        }
    }

    newContent, err := editor.ApplyModifications()
    if err != nil {
        return err
    }

    return os.WriteFile(buildFile, []byte(newContent), 0644)
}

// Usage
pluginVersions := map[string]string{
    "org.springframework.boot":           "2.7.2",
    "io.spring.dependency-management":    "1.0.12.RELEASE",
    "org.jetbrains.kotlin.jvm":          "1.7.10",
}

err := updateAllPlugins("build.gradle", pluginVersions)
if err != nil {
    log.Fatal(err)
}

Modification Tracking ​

Viewing Modifications ​

Track what changes will be made before applying them:

go
editor, err := api.CreateGradleEditor("build.gradle")
if err != nil {
    log.Fatal(err)
}

// Make some changes
editor.UpdateDependencyVersion("mysql", "mysql-connector-java", "8.0.31")
editor.UpdatePluginVersion("org.springframework.boot", "2.7.2")
editor.UpdateProperty("version", "1.0.0")

// Review modifications before applying
modifications := editor.GetModifications()
fmt.Printf("Planned modifications: %d\n", len(modifications))

for i, mod := range modifications {
    fmt.Printf("Modification %d:\n", i+1)
    fmt.Printf("  Type: %s\n", mod.Type)
    fmt.Printf("  Description: %s\n", mod.Description)
    fmt.Printf("  Old text: %s\n", mod.OldText)
    fmt.Printf("  New text: %s\n", mod.NewText)
    fmt.Printf("  Location: Line %d, Column %d\n", 
        mod.SourceRange.Start.Line, mod.SourceRange.Start.Column)
}

Conditional Modifications ​

Apply modifications based on conditions:

go
editor, err := api.CreateGradleEditor("build.gradle")
if err != nil {
    log.Fatal(err)
}

// Get current project info
project := editor.GetSourceMappedProject()

// Conditional updates based on current state
for _, dep := range project.SourceMappedDependencies {
    dependency := dep.Dependency
    
    // Update Spring dependencies to latest version
    if strings.Contains(dependency.Group, "springframework") {
        err = editor.UpdateDependencyVersion(
            dependency.Group, 
            dependency.Name, 
            "5.3.21"
        )
        if err != nil {
            log.Printf("Failed to update %s: %v", dependency.Name, err)
        }
    }
    
    // Update test dependencies
    if dependency.Scope == "testImplementation" && 
       strings.Contains(dependency.Group, "junit") {
        err = editor.UpdateDependencyVersion(
            dependency.Group,
            dependency.Name,
            "5.8.2"
        )
        if err != nil {
            log.Printf("Failed to update %s: %v", dependency.Name, err)
        }
    }
}

Error Handling ​

Graceful Error Handling ​

Handle various error scenarios gracefully:

go
func safeUpdateDependency(buildFile, group, name, version string) error {
    editor, err := api.CreateGradleEditor(buildFile)
    if err != nil {
        return fmt.Errorf("failed to create editor: %v", err)
    }

    err = editor.UpdateDependencyVersion(group, name, version)
    if err != nil {
        if strings.Contains(err.Error(), "not found") {
            log.Printf("Dependency %s:%s not found, skipping", group, name)
            return nil // Not an error, just skip
        }
        return fmt.Errorf("failed to update dependency: %v", err)
    }

    newContent, err := editor.ApplyModifications()
    if err != nil {
        return fmt.Errorf("failed to apply modifications: %v", err)
    }

    err = os.WriteFile(buildFile, []byte(newContent), 0644)
    if err != nil {
        return fmt.Errorf("failed to write file: %v", err)
    }

    log.Printf("Successfully updated %s:%s to %s", group, name, version)
    return nil
}

Validation Before Modification ​

go
func validateAndUpdate(buildFile string) error {
    // First, parse and validate the file
    result, err := api.ParseFile(buildFile)
    if err != nil {
        return fmt.Errorf("file validation failed: %v", err)
    }

    if len(result.Warnings) > 0 {
        log.Printf("File has %d warnings, proceeding with caution", len(result.Warnings))
    }

    // Check if required dependencies exist
    requiredDeps := map[string]bool{
        "mysql:mysql-connector-java": false,
        "org.springframework:spring-core": false,
    }

    for _, dep := range result.Project.Dependencies {
        key := fmt.Sprintf("%s:%s", dep.Group, dep.Name)
        if _, exists := requiredDeps[key]; exists {
            requiredDeps[key] = true
        }
    }

    // Only proceed if all required dependencies are found
    for depKey, found := range requiredDeps {
        if !found {
            return fmt.Errorf("required dependency not found: %s", depKey)
        }
    }

    // Proceed with modifications
    editor, err := api.CreateGradleEditor(buildFile)
    if err != nil {
        return err
    }

    // Apply updates...
    return nil
}

Minimal Diff Guarantee ​

The structured editor is designed to make minimal changes:

Before and After Comparison ​

gradle
// Original build.gradle
plugins {
    id 'java'
    id 'org.springframework.boot' version '2.7.0'
}

group = 'com.example'
version = '0.1.0-SNAPSHOT'

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'mysql:mysql-connector-java:8.0.28'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

After updating MySQL version to 8.0.31:

gradle
// Modified build.gradle
plugins {
    id 'java'
    id 'org.springframework.boot' version '2.7.0'
}

group = 'com.example'
version = '0.1.0-SNAPSHOT'

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'mysql:mysql-connector-java:8.0.31'  // Only this line changed
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

Preserving Formatting ​

The editor preserves:

  • Original indentation and spacing
  • Comments and their positions
  • Line breaks and empty lines
  • Quote styles (single vs double quotes)
  • Formatting within blocks

Best Practices ​

go
// Good: Batch related changes together
editor, _ := api.CreateGradleEditor("build.gradle")
editor.UpdateDependencyVersion("mysql", "mysql-connector-java", "8.0.31")
editor.UpdateDependencyVersion("org.springframework", "spring-core", "5.3.21")
editor.UpdatePluginVersion("org.springframework.boot", "2.7.2")
newContent, _ := editor.ApplyModifications()

// Avoid: Multiple separate edit operations
api.UpdateDependencyVersion("build.gradle", "mysql", "mysql-connector-java", "8.0.31")
api.UpdateDependencyVersion("build.gradle", "org.springframework", "spring-core", "5.3.21")
api.UpdatePluginVersion("build.gradle", "org.springframework.boot", "2.7.2")

2. Validate Before Editing ​

go
// Always validate the file can be parsed before editing
result, err := api.ParseFile("build.gradle")
if err != nil {
    return fmt.Errorf("cannot edit invalid file: %v", err)
}

// Proceed with editing
editor, err := api.CreateGradleEditor("build.gradle")
// ...

3. Handle Missing Dependencies Gracefully ​

go
err = editor.UpdateDependencyVersion("group", "name", "version")
if err != nil && strings.Contains(err.Error(), "not found") {
    // Dependency doesn't exist, add it instead
    err = editor.AddDependency("group", "name", "version", "implementation")
}

Next Steps ​

Released under the MIT License.