Skip to content

Latest commit

 

History

History
343 lines (271 loc) · 7.21 KB

File metadata and controls

343 lines (271 loc) · 7.21 KB

Migration System

Overview

The migration system converts various build systems to Builder's Builderfile format. It uses a parse-transform-emit architecture with an intermediate representation (IR).

Design

Architecture

Input File
     │
     ▼
IMigrator (Interface)
     │
     ▼
Parse & Extract (System-specific)
     │
     ▼
MigrationTarget (IR)
     │
     ▼
BuilderfileEmitter (DSL generation)
     │
     ▼
Builderfile

Key Principles

  1. Unified IR: All build systems share common concepts (targets, sources, dependencies). The IR captures these universals.

  2. Registry Pattern: Central registration of migrators with factory creation and auto-detection.

  3. Result-Based Errors: All operations return Result!(T, BuildError) for type-safe error handling.

  4. Strong Typing: Uses Target, TargetType, TargetLanguage from schema for compile-time safety.

Module Structure

infrastructure/migration/
├── core/
│   ├── common.d    - IR types (MigrationTarget, MigrationResult)
│   ├── base.d      - IMigrator interface, BaseMigrator
│   └── package.d
├── registry/
│   ├── registry.d  - MigratorRegistry (singleton)
│   └── package.d
├── emission/
│   ├── emitter.d   - BuilderfileEmitter (DSL generation)
│   └── package.d
├── systems/        - Individual migrators
│   ├── bazel.d
│   ├── cmake.d
│   ├── maven.d
│   ├── gradle.d
│   ├── make.d
│   ├── cargo.d
│   ├── npm.d
│   ├── gomod.d
│   ├── dub.d
│   ├── sbt.d
│   ├── meson.d
│   └── package.d
└── package.d

Key Types

MigrationTarget

Intermediate representation:

struct MigrationTarget
{
    string name;
    TargetType type;          // Executable, Library, Test, Custom
    TargetLanguage language;  // C, Cpp, Python, Go, etc.
    string[] sources;
    string[] dependencies;
    string[] flags;
    string[] includes;
    string output;
    string[string] env;
    string[string] metadata;  // System-specific data
    
    Target toTarget() const;  // Convert to Builder schema
}

MigrationResult

struct MigrationResult
{
    MigrationTarget[] targets;
    MigrationWarning[] warnings;
    string[string] globalConfig;
    bool success;
    
    bool hasErrors() const;
    bool hasWarnings() const;
    MigrationWarning[] errors() const;
}

IMigrator

interface IMigrator
{
    string systemName() const;
    string[] defaultFileNames() const;
    bool canMigrate(string filePath) const;
    Result!(MigrationResult, BuildError) migrate(string inputPath);
    
    string description() const;
    string[] supportedFeatures() const;
    string[] limitations() const;
}

Warning Levels

enum WarningLevel
{
    Info,      // Informational
    Warning,   // Should review
    Error      // Critical issue
}

struct MigrationWarning
{
    WarningLevel level;
    string message;
    string context;
    string[] suggestions;
}

Supported Systems

System File Language Inference
Bazel BUILD, BUILD.bazel Rule names (cc_binary → C++)
CMake CMakeLists.txt Commands (add_executable → C++)
Maven pom.xml <packaging> element
Gradle build.gradle, build.gradle.kts Plugins applied
Make Makefile Targets and recipes
Cargo Cargo.toml Always Rust
npm package.json Scripts and dependencies
Go go.mod Always Go
DUB dub.json, dub.sdl Always D
SBT build.sbt Always Scala
Meson meson.build project() language

Parsing Strategy

Each migrator:

  1. Reads input file once
  2. Extracts all relevant information
  3. Transforms to IR immediately
  4. Validates during extraction

No multi-pass parsing.

Parser Types

Format Approach
JSON Standard library
Regex Simple pattern matching (Bazel, Make)
XML Pattern matching (Maven)
Custom Context-specific (CMake, Meson)

BuilderfileEmitter

Location: source/infrastructure/migration/emission/emitter.d

Generates clean DSL:

  • Indentation-aware nesting
  • Idiomatic syntax
  • Warnings as comments
  • Metadata preserved

Example output:

// Builderfile
// Auto-generated by Builder migration tool
// Review and adjust as needed

target("hello") {
    type: executable;
    language: cpp;
    sources: ["main.cpp", "greeter.cpp"];
    deps: [":utils"];
    flags: ["-std=c++17", "-Wall"];
    
    // Additional metadata:
    // linkopts: -lpthread
}

// Migration Summary
// =================
// WARNINGS:
//   - Linker flags require manual configuration
//     Context: linkopts

MigratorRegistry

Location: source/infrastructure/migration/registry/registry.d

class MigratorRegistry
{
    static MigratorRegistry getInstance();
    
    void register(IMigrator migrator);
    IMigrator create(string systemName);
    bool isSupported(string systemName) const;
    string[] availableSystems() const;
    IMigrator[] allMigrators();
}

Usage

Auto-Detection

auto migrator = MigratorFactory.autoDetect(filePath);
if (migrator !is null)
{
    auto result = migrator.migrate(filePath);
    if (result.isOk)
    {
        auto emitter = BuilderfileEmitter();
        auto builderfile = emitter.emit(result.unwrap());
        writeFile("Builderfile", builderfile);
    }
}

Explicit System

auto migrator = MigratorFactory.create("bazel");
auto result = migrator.migrate("BUILD");

CLI

# Auto-detect
bldr migrate BUILD

# Explicit system
bldr migrate --from=cmake CMakeLists.txt

# Dry run (show output, don't write)
bldr migrate --dry-run pom.xml

# List supported systems
bldr migrate --list

Adding a Migrator

  1. Create migrator class:
final class NewSystemMigrator : BaseMigrator
{
    override string systemName() const { return "newsystem"; }
    override string[] defaultFileNames() const { return ["build.new"]; }
    override bool canMigrate(string path) const { /* ... */ }
    override Result!(MigrationResult, BuildError) migrate(string path)
    {
        auto contentResult = readInputFile(path);
        if (contentResult.isErr)
            return contentResult.err;
        
        auto content = contentResult.unwrap();
        MigrationTarget[] targets;
        MigrationWarning[] warnings;
        
        // Parse and extract...
        
        return createResult(targets, warnings);
    }
}
  1. Register in registry.d:
private void registerMigrators()
{
    register(new NewSystemMigrator());
    // ...
}
  1. Add to systems package:
public import infrastructure.migration.systems.newsystem;

Performance

Migration is I/O bound:

  • Small files (<100 lines): <10ms
  • Medium files (100-1000 lines): <50ms
  • Large files (>1000 lines): <200ms

Strategies:

  • Compiled regex (reused)
  • Single-pass parsing
  • No backtracking

Testing

Unit tests per migrator:

  • Valid input parsing
  • Edge cases (empty, minimal, maximal)
  • Error handling
  • Feature coverage

Integration tests:

  • Auto-detection
  • CLI commands
  • End-to-end migration

Real-world validation:

  • Test against actual project build files
  • Compare output to manual migration