Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
default: run
run: dep1 dep2
echo "Hello World \n " > hello.txt
clean:
rm -rf hello dep1.txt dep2.txt
dep1:
echo "Hello dep1 \n " > dep1.txt
dep2: dep1
echo "Hello dep2 \n " > dep2.txt
run: dep1
echo "no \n " > hello
73 changes: 71 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,71 @@
# Makego-Fatma-Ebrahim
a simple build automation tool similar to Make, but with a simpler featureset.
# Makego
A simple build automation tool similar to Make, but with a simpler featureset.

The tool is able to execute targets with dependencies and commands, focusing on the core functionality of a build system.

## Functions:
### `Make() error`
Make is a function that parse a makefile specified by the command line flag -f or the makefile in the current directory then execute the targets specified by the command line arguments or execute the default target

## Features:
### 1. Configuration File:
- Parse a custom configuration file `Makefile`
- Support defining targets, their dependencies, and associated commands

### 2. Dependency Resolution:
- Detect and report circular dependencies

### 3. Command Execution:
- Execute shell commands associated with each target
- Support running multiple commands for a single target

### 4. CLI Interface:
- Accept target names as command-line arguments
- If no target is specified, run the default target (if defined)
- Accept a custom Makefile via optional '-f' flag

### 5. Concurrency:
- Execute independent targets concurrently using goroutines
- Implement proper synchronization for dependent targets

### 6. Error Handling:
- Handle and report errors in Makefile parsing and commands execution

## How to Use:
### Step 1: Install the tool using `go get`

```bash
go get github.com/codescalersinternships/Makego-Fatma-Ebrahim
```

This command fetches the tool and adds it to your project's `go.mod` file.

### Step 2: Import and use the tool in your code

After running `go get`, you can import the tool into your project and use the functions as described:
```
package main

import (
"fmt"

makego "github.com/codescalersinternships/Makego-Fatma-Ebrahim/pkg"
)

func main() {
err := makego.Make()
if err != nil {
fmt.Println(err)
}
}
```

### Step 3: Specify the targets
You can use the default target or specify targets via command-line arguments as follows:
```
go run main.go clean
```
Also, you can specify a custom Makefile via command-line flag as follows:
```
go run main.go -f ../Makefile
```
14 changes: 14 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package main

import (
"fmt"

makego "github.com/codescalersinternships/Makego-Fatma-Ebrahim/pkg"
)

func main() {
err := makego.Make()
if err != nil {
fmt.Println(err)
}
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/codescalersinternships/Makego-Fatma-Ebrahim

go 1.25.1
147 changes: 147 additions & 0 deletions pkg/makego.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package makego

import (
"flag"
"fmt"
"os/exec"
"strings"
"sync"
)

func sortDependencies(target Target, alltargets map[string]Target, visited map[string]bool) ([]string, error) {
dep_list := []string{}
err := error(nil)
for _, dep := range target.Dependencies {
if visited[dep] {
continue
}
visited[dep] = true
_, ok := alltargets[dep]
if !ok {
err = fmt.Errorf("dependency '%s' not found", dep)
}
sorted_deps, _ := sortDependencies(alltargets[dep], alltargets, visited)

dep_list = append(dep_list, sorted_deps...)
}
dep_list = append(dep_list, target.Name)
return dep_list, err
}

func detectCycle(dep_list []string) bool {
dep_map := make(map[string]bool)
for _, dep := range dep_list {
_, ok := dep_map[dep]
if ok {
return true
}
dep_map[dep] = true
}
return false

}

func getDependencies(targets map[string]Target) (map[string][]string, error) {
dependency_map := make(map[string][]string)
err := error(nil)
for tar_name, target := range targets {
visited := make(map[string]bool)
dep_list, e := sortDependencies(target, targets, visited)
if e != nil {
err = e
}
if detectCycle(dep_list) {
return nil, fmt.Errorf("circular dependency detected")
}
dependency_map[tar_name] = dep_list
}
return dependency_map, err
}

func getDefaultTarget(targets map[string]Target) ([]string, error) {
cliTargets := make([]string, 0)
flag.Parse()
for arg := range flag.Args() {
targetName := flag.Arg(arg)
_, ok := targets[targetName]
if !ok {
return nil, fmt.Errorf("target '%s' not found", targetName)
}
cliTargets = append(cliTargets, targetName)
}
if len(cliTargets) == 0 {
cliTargets = append(cliTargets, "default")
}
return cliTargets, nil
}

func executeTarget(targets map[string]Target, dependencies []string, executed map[string]bool, mu *sync.Mutex) error {
for _, dep := range dependencies {
mu.Lock()
_, ok := executed[dep]
if ok {
mu.Unlock()
continue
}
executed[dep] = true
for _, command := range targets[dep].Commands {
fmt.Println(command)
cmd := exec.Command("sh", "-c", command)
err := cmd.Run()
if err != nil {
return fmt.Errorf("error in command: '%s' in target: '%s' with message: %v", command, dep, err)
}
}
mu.Unlock()

}
return nil
}

func executeTargetsConcurrently(targets map[string]Target, cliTargets []string, dependencies map[string][]string) error {
wg := sync.WaitGroup{}
executed := make(map[string]bool)
mu := &sync.Mutex{}
for _, cliTarget := range cliTargets {
wg.Go(func() { executeTarget(targets, dependencies[cliTarget], executed, mu) })
}
wg.Wait()
return nil
}

func makeHandler(cliTargets []string, targets map[string]Target) error {
err := error(nil)

dep_map, e := getDependencies(targets)
// ignore errors of incorrect dependencies
if e != nil && !strings.HasPrefix(e.Error(), "dependency") {
return e
}
if e != nil && strings.HasPrefix(e.Error(), "dependency") {
err = e
}

e = executeTargetsConcurrently(targets, cliTargets, dep_map)
if e != nil {
return e
}
return err
}

// Make is a function that parse a makefile specified by the command line flag -f
// or the makefile in the current directory
// then execute the targets specified by the command line arguments
// or execute the default target
func Make() error {
targets, e := parse()
if e != nil {
return e
}

cliTargets, e := getDefaultTarget(targets)
if e != nil {
return e
}

return makeHandler(cliTargets, targets)
}
Loading