build-graph 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 93ccee936d5902138b3cd45b565d8dfa20a477d6
4
- data.tar.gz: 221ef37fa2440af4b5b12be9cc209402c741096d
3
+ metadata.gz: 77b51e210994a3c93e59ddebf8b3aead06b84c07
4
+ data.tar.gz: 5a9ece86c313dbbf9a0ddd462cd18b10eed49ff5
5
5
  SHA512:
6
- metadata.gz: 3bebcf37fa8a68d1f602317d39af3fbe778687baea8cd9fc1fdbe93df20ea1964402183aa052b166557293fea0eeefb183c27735e46dd7a5261a2b8fb7483b16
7
- data.tar.gz: cb91ae793eaa9ae002060d3bc407255376a4a5a3a7592fb0912d306a4a972ef872c77b9ac9a4ca0f192503ee926e1f1c7a0e868cf3a31514ae73e39af669b6fe
6
+ metadata.gz: afb78262e92072da8e29dce01898447246e7f718a65b4c5f25a79f049fb9474a0fa21a1685758c11720d0f7cbc1b4c7ee328845da1adaf52a67b860da261c69d
7
+ data.tar.gz: 4395c36481c93ada46363d602f34e136d9a8759e1022a3f669e97abe05f957ec7b2cab09fe81b06c38f7b796c5baf3a3261b5cb4ebca9e55d9e134f837479e4d
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - "2.0"
4
+ - "2.1"
data/README.md CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  Build::Graph is a framework for build systems, with specific functionality for dealing with file based processes.
4
4
 
5
+ [![Build Status](https://secure.travis-ci.org/ioquatix/build-graph.png)](http://travis-ci.org/ioquatix/build-graph)
6
+ [![Code Climate](https://codeclimate.com/github/ioquatix/build-graph.png)](https://codeclimate.com/github/ioquatix/build-graph)
7
+
5
8
  ## Installation
6
9
 
7
10
  Add this line to your application's Gemfile:
@@ -16,6 +19,10 @@ Or install it yourself as:
16
19
 
17
20
  $ gem install build-graph
18
21
 
22
+ ### Naming
23
+
24
+ I'd like to call this gem, simply, `build`, but this name is not available.
25
+
19
26
  ## Usage
20
27
 
21
28
  TODO: Write usage instructions here
@@ -20,6 +20,8 @@ Gem::Specification.new do |spec|
20
20
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
21
  spec.require_paths = ["lib"]
22
22
 
23
+ spec.add_dependency "process-group", "~> 0.1.0"
24
+
23
25
  spec.add_dependency "system"
24
26
  spec.add_dependency "rainbow"
25
27
 
@@ -25,27 +25,15 @@ module Build
25
25
  module Files
26
26
  class List
27
27
  include Enumerable
28
-
28
+
29
29
  def +(list)
30
30
  Composite.new([self, list])
31
31
  end
32
-
32
+
33
33
  def intersects? other
34
34
  other.any?{|path| include?(path)}
35
35
  end
36
-
37
- def rebase(root)
38
- raise NotImplementedError
39
- end
40
-
41
- def to_paths
42
- relative_paths = self.each do |path|
43
- path.relative_path
44
- end
45
36
 
46
- return Paths.new(@root, relative_paths)
47
- end
48
-
49
37
  def match(pattern)
50
38
  all? {|path| path.match(pattern)}
51
39
  end
@@ -68,13 +56,48 @@ module Build
68
56
  end
69
57
  end
70
58
 
71
- class Directory < List
72
- def initialize(root, path = "")
59
+ # A list which has a single root directory.
60
+ class DirectoryList < List
61
+ def initialize(root)
73
62
  @root = root.to_s
63
+ end
64
+
65
+ attr :root
66
+
67
+ def roots
68
+ [@root]
69
+ end
70
+
71
+ def rebase(root)
72
+ raise NotImplementedError
73
+ end
74
+
75
+ def to_paths(root=@root)
76
+ relative_paths = self.each do |path|
77
+ path.relative_path
78
+ end
79
+
80
+ return Paths.new(root, relative_paths)
81
+ end
82
+
83
+ def process(root=@root)
84
+ self.collect do |path|
85
+ basename, _, filename = path.relative_path.rpartition(File::SEPARATOR)
86
+
87
+ File.join(basename, yield(filename))
88
+ end
89
+
90
+ Paths.new(root, self.collect)
91
+ end
92
+ end
93
+
94
+ class Directory < DirectoryList
95
+ def initialize(root, path = "")
96
+ super(root)
97
+
74
98
  @path = path
75
99
  end
76
100
 
77
- attr :root
78
101
  attr :path
79
102
 
80
103
  def full_path
@@ -82,15 +105,11 @@ module Build
82
105
  end
83
106
 
84
107
  def each(&block)
85
- Dir.glob(full_path + "**/*").each do |path|
108
+ Dir.glob(full_path + "**/*") do |path|
86
109
  yield RelativePath.new(path, @root)
87
110
  end
88
111
  end
89
112
 
90
- def roots
91
- [full_path]
92
- end
93
-
94
113
  def eql?(other)
95
114
  other.kind_of?(self.class) and @root.eql?(other.root) and @path.eql?(other.path)
96
115
  end
@@ -109,9 +128,10 @@ module Build
109
128
  end
110
129
  end
111
130
 
112
- class Glob < List
131
+ class Glob < DirectoryList
113
132
  def initialize(root, pattern)
114
- @root = root.to_s
133
+ super(root)
134
+
115
135
  @pattern = pattern
116
136
  end
117
137
 
@@ -124,15 +144,11 @@ module Build
124
144
 
125
145
  # Enumerate all paths matching the pattern.
126
146
  def each(&block)
127
- Dir.glob(full_pattern).each do |path|
147
+ Dir.glob(full_pattern) do |path|
128
148
  yield RelativePath.new(path, @root)
129
149
  end
130
150
  end
131
-
132
- def roots
133
- [@root]
134
- end
135
-
151
+
136
152
  def eql?(other)
137
153
  other.kind_of?(self.class) and @root.eql?(other.root) and @pattern.eql?(other.pattern)
138
154
  end
@@ -150,9 +166,10 @@ module Build
150
166
  end
151
167
  end
152
168
 
153
- class Paths < List
169
+ class Paths < DirectoryList
154
170
  def initialize(root, paths)
155
- @root = root.to_s
171
+ super(root)
172
+
156
173
  @paths = Array(paths)
157
174
  end
158
175
 
@@ -165,10 +182,6 @@ module Build
165
182
  end
166
183
  end
167
184
 
168
- def roots
169
- [@root]
170
- end
171
-
172
185
  def eql? other
173
186
  other.kind_of?(self.class) and @paths.eql?(other.paths)
174
187
  end
@@ -20,102 +20,102 @@
20
20
 
21
21
  require 'set'
22
22
 
23
- require 'build/system/state'
23
+ require 'build/files/state'
24
24
 
25
25
  module Build
26
- module System
26
+ module Files
27
27
  class Monitor
28
28
  def initialize
29
29
  @directories = Hash.new { |hash, key| hash[key] = Set.new }
30
-
30
+
31
31
  @updated = false
32
32
  end
33
-
33
+
34
34
  attr :updated
35
-
35
+
36
36
  # Notify the monitor that files in these directories have changed.
37
37
  def update(directories, *args)
38
38
  directories.each do |directory|
39
39
  # directory = File.realpath(directory)
40
-
40
+
41
41
  @directories[directory].each do |handle|
42
42
  handle.changed!(*args)
43
43
  end
44
44
  end
45
45
  end
46
-
46
+
47
47
  def roots
48
48
  @directories.keys
49
49
  end
50
-
50
+
51
51
  def delete(handle)
52
52
  handle.directories.each do |directory|
53
53
  @directories[directory].delete(handle)
54
-
54
+
55
55
  # Remove the entire record if there are no handles:
56
56
  if @directories[directory].size == 0
57
57
  @directories.delete(directory)
58
-
58
+
59
59
  @updated = true
60
60
  end
61
61
  end
62
62
  end
63
-
63
+
64
64
  def track_changes(files, &block)
65
65
  handle = Handle.new(self, files, &block)
66
-
66
+
67
67
  add(handle)
68
68
  end
69
-
69
+
70
70
  def add(handle)
71
71
  handle.directories.each do |directory|
72
72
  @directories[directory] << handle
73
-
73
+
74
74
  # We just added the first handle:
75
75
  if @directories[directory].size == 1
76
76
  # If the handle already existed, this might trigger unnecessarily.
77
77
  @updated = true
78
78
  end
79
79
  end
80
-
80
+
81
81
  handle
82
82
  end
83
83
  end
84
-
84
+
85
85
  def self.run_with_fsevent(monitor, options = {}, &block)
86
86
  require 'rb-fsevent'
87
-
87
+
88
88
  fsevent ||= FSEvent.new
89
-
89
+
90
90
  catch(:interrupt) do
91
91
  while true
92
92
  fsevent.watch monitor.roots do |directories|
93
93
  monitor.update(directories)
94
-
94
+
95
95
  yield
96
-
96
+
97
97
  if monitor.updated
98
98
  fsevent.stop
99
99
  end
100
100
  end
101
-
101
+
102
102
  fsevent.run
103
103
  end
104
104
  end
105
105
  end
106
-
106
+
107
107
  def self.run_with_polling(monitor, options = {}, &block)
108
108
  catch(:interrupt) do
109
109
  while true
110
110
  monitor.update(monitor.roots)
111
-
111
+
112
112
  yield
113
-
113
+
114
114
  sleep(options[:latency] || 5.0)
115
115
  end
116
116
  end
117
117
  end
118
-
118
+
119
119
  def self.run(monitor, options = {}, &block)
120
120
  run_with_polling(monitor, options, &block)
121
121
  end
@@ -19,7 +19,7 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module Build
22
- module System
22
+ module Files
23
23
  # Represents a specific file on disk with a specific mtime.
24
24
  class FileTime
25
25
  include Comparable
@@ -36,7 +36,7 @@ module Build
36
36
  @time <=> other.time
37
37
  end
38
38
  end
39
-
39
+
40
40
  class State
41
41
  def initialize(files)
42
42
  raise ArgumentError.new("Invalid files list: #{files}") unless Files::List === files
@@ -1,6 +1,5 @@
1
1
 
2
- require 'build/system/monitor'
3
- require 'build/system/pool'
2
+ require 'build/files/monitor'
4
3
 
5
4
  require 'build/error'
6
5
  require 'build/node'
@@ -8,7 +7,7 @@ require 'build/walker'
8
7
  require 'build/edge'
9
8
 
10
9
  module Build
11
- class Graph < System::Monitor
10
+ class Graph < Files::Monitor
12
11
  def initialize
13
12
  super
14
13
 
@@ -1,12 +1,12 @@
1
1
 
2
- require 'build/system/state'
2
+ require 'build/files/state'
3
3
 
4
4
  module Build
5
5
  class Node
6
6
  def initialize(graph, inputs, outputs)
7
7
  @graph = graph
8
8
 
9
- @state = System::IOState.new(inputs, outputs)
9
+ @state = Files::IOState.new(inputs, outputs)
10
10
 
11
11
  @status = :unknown
12
12
  @fiber = nil
@@ -1,3 +1,3 @@
1
1
  module Build
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -0,0 +1,18 @@
1
+
2
+ #include <iostream>
3
+ #include <vector>
4
+
5
+ std::vector<int> get_numbers() {
6
+ return {10, 20, 30, 40, 50, 60, 80};
7
+ }
8
+
9
+ int main (int argc, char ** argv)
10
+ {
11
+ auto numbers = get_numbers();
12
+ std::sort(numbers.begin(), numbers.end());
13
+
14
+ for (auto & number : numbers)
15
+ std::cerr << number << "?" << std::endl;
16
+
17
+ return 0;
18
+ }
@@ -60,4 +60,15 @@ class TestFiles < Test::Unit::TestCase
60
60
  # We actually return a subclass which includes the root portion:
61
61
  assert_equal __dir__, test_glob.first.root
62
62
  end
63
+
64
+ def test_renaming
65
+ program_root = File.join(__dir__, "program")
66
+ program_glob = Build::Files::Glob.new(program_root, "*.cpp")
67
+
68
+ paths = program_glob.process do |path|
69
+ path + ".o"
70
+ end
71
+
72
+ puts "object paths: #{paths} from program paths: #{program_glob.to_a}"
73
+ end
63
74
  end
@@ -22,7 +22,9 @@ require 'test/unit'
22
22
 
23
23
  require 'build/graph'
24
24
  require 'build/files'
25
- require 'build/system/pool'
25
+
26
+ require 'process/group'
27
+ require 'fileutils'
26
28
 
27
29
  class TestGraph < Test::Unit::TestCase
28
30
  # The graph node is created once, so a graph has a fixed number of nodes, which store per-vertex state and connectivity.
@@ -49,14 +51,14 @@ class TestGraph < Test::Unit::TestCase
49
51
 
50
52
  # The task is the context in which a vertex is updated. Because nodes may initially create other nodes, it is also responsible for looking up and creating new nodes.
51
53
  class Task < Build::Task
52
- def initialize(graph, walker, node, pool = nil)
54
+ def initialize(graph, walker, node, group = nil)
53
55
  super(graph, walker, node)
54
56
 
55
- @pool = pool
57
+ @group = group
56
58
  end
57
59
 
58
60
  def wet?
59
- @pool# and @node.dirty?
61
+ @group# and @node.dirty?
60
62
  end
61
63
 
62
64
  def process(inputs, outputs, &block)
@@ -72,10 +74,10 @@ class TestGraph < Test::Unit::TestCase
72
74
 
73
75
  def run(*arguments)
74
76
  if wet?
75
- status = @pool.run(*arguments)
77
+ status = @group.spawn(*arguments)
76
78
 
77
79
  if status != 0
78
- raise CommandFailure.new(arguments, status)
80
+ raise RuntimeError.new(status)
79
81
  end
80
82
  end
81
83
  end
@@ -88,8 +90,8 @@ class TestGraph < Test::Unit::TestCase
88
90
  end
89
91
 
90
92
  class Graph < Build::Graph
91
- def initialize
92
- yield self
93
+ def initialize(&block)
94
+ @top = Node.new(self, Build::Files::NONE, Build::Files::NONE, &block)
93
95
 
94
96
  super()
95
97
  end
@@ -101,23 +103,19 @@ class TestGraph < Test::Unit::TestCase
101
103
  end
102
104
 
103
105
  def build_graph!
104
- puts "Building graph..."
105
-
106
106
  super do |walker, node|
107
107
  Task.new(self, walker, node)
108
108
  end
109
109
  end
110
110
 
111
111
  def update!
112
- puts "Updating graph..."
113
-
114
- pool = Build::System::Pool.new
112
+ group = Process::Group.new
115
113
 
116
114
  super do |walker, node|
117
- Task.new(self, walker, node, pool)
115
+ Task.new(self, walker, node, group)
118
116
  end
119
117
 
120
- pool.wait
118
+ group.wait
121
119
  end
122
120
  end
123
121
 
@@ -127,11 +125,9 @@ class TestGraph < Test::Unit::TestCase
127
125
 
128
126
  FileUtils.rm_f output_paths.to_a
129
127
 
130
- graph = Graph.new do |graph|
131
- graph.top = Node.new(graph, Build::Files::NONE, Build::Files::NONE) do
132
- process test_glob, output_paths do
133
- run("ls", "-la", *test_glob, :out => output_paths.first)
134
- end
128
+ graph = Graph.new do
129
+ process test_glob, output_paths do
130
+ run("ls", "-la", *test_glob, :out => output_paths.first)
135
131
  end
136
132
  end
137
133
 
@@ -144,5 +140,9 @@ class TestGraph < Test::Unit::TestCase
144
140
  assert_equal mtime, File.mtime(output_paths.first)
145
141
 
146
142
  FileUtils.rm_f output_paths.to_a
143
+
144
+ #graph.nodes.each do |key, node|
145
+ # puts "#{node.status} #{node.inspect}"
146
+ #end
147
147
  end
148
148
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: build-graph
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-03-06 00:00:00.000000000 Z
11
+ date: 2014-03-25 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: process-group
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: 0.1.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 0.1.0
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: system
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -76,6 +90,7 @@ extensions: []
76
90
  extra_rdoc_files: []
77
91
  files:
78
92
  - .gitignore
93
+ - .travis.yml
79
94
  - Gemfile
80
95
  - README.md
81
96
  - Rakefile
@@ -84,13 +99,13 @@ files:
84
99
  - lib/build/edge.rb
85
100
  - lib/build/error.rb
86
101
  - lib/build/files.rb
102
+ - lib/build/files/monitor.rb
103
+ - lib/build/files/state.rb
87
104
  - lib/build/graph.rb
88
105
  - lib/build/node.rb
89
- - lib/build/system/monitor.rb
90
- - lib/build/system/pool.rb
91
- - lib/build/system/state.rb
92
106
  - lib/build/version.rb
93
107
  - lib/build/walker.rb
108
+ - test/program/main.cpp
94
109
  - test/test_files.rb
95
110
  - test/test_graph.rb
96
111
  homepage: ''
@@ -119,5 +134,6 @@ specification_version: 4
119
134
  summary: Build::Graph is a framework for build systems, with specific functionality
120
135
  for dealing with file based processes.
121
136
  test_files:
137
+ - test/program/main.cpp
122
138
  - test/test_files.rb
123
139
  - test/test_graph.rb
@@ -1,116 +0,0 @@
1
- # Copyright, 2014, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
- #
3
- # Permission is hereby granted, free of charge, to any person obtaining a copy
4
- # of this software and associated documentation files (the "Software"), to deal
5
- # in the Software without restriction, including without limitation the rights
6
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- # copies of the Software, and to permit persons to whom the Software is
8
- # furnished to do so, subject to the following conditions:
9
- #
10
- # The above copyright notice and this permission notice shall be included in
11
- # all copies or substantial portions of the Software.
12
- #
13
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- # THE SOFTWARE.
20
-
21
- require 'rainbow'
22
- require 'system'
23
- require 'fiber'
24
-
25
- module Build
26
- module System
27
- # A pool is a group of tasks which can be run asynchrnously using fibers. Someone must call #wait to ensure that all fibers eventuall resume.
28
- class Pool
29
- def self.processor_count
30
- ::System::CPU.count
31
- end
32
-
33
- class Command
34
- def initialize(arguments, options, fiber = Fiber.current)
35
- @arguments = arguments
36
- @options = options
37
-
38
- @fiber = fiber
39
- end
40
-
41
- attr :arguments
42
- attr :options
43
-
44
- def run(options = {})
45
- puts Rainbow("Running #{@arguments.inspect} options: #{@options.merge(options).inspect}").blue
46
-
47
- Process.spawn(*@arguments, @options.merge(options))
48
- end
49
-
50
- def resume(*arguments)
51
- @fiber.resume(*arguments)
52
- end
53
- end
54
-
55
- def initialize(options = {})
56
- @commands = []
57
- @limit = options[:limit] || Pool.processor_count
58
-
59
- @running = {}
60
- @fiber = nil
61
-
62
- @pgid = true
63
- end
64
-
65
- attr :running
66
-
67
- def run(*arguments)
68
- options = Hash === arguments.last ? arguments.pop : {}
69
- arguments = arguments.flatten.collect &:to_s
70
-
71
- @commands << Command.new(arguments, options)
72
-
73
- schedule!
74
-
75
- Fiber.yield
76
- end
77
-
78
- def schedule!
79
- while @running.size < @limit and @commands.size > 0
80
- command = @commands.shift
81
-
82
- if @running.size == 0
83
- pid = command.run(:pgroup => true)
84
- @pgid = Process.getpgid(pid)
85
- else
86
- pid = command.run(:pgroup => @pgid)
87
- end
88
-
89
- @running[pid] = command
90
- end
91
- end
92
-
93
- def wait
94
- while @running.size > 0
95
- # Wait for processes in this group:
96
- pid, status = Process.wait2(-@pgid)
97
-
98
- command = @running.delete(pid)
99
-
100
- schedule!
101
-
102
- command.resume(status)
103
- end
104
- end
105
- end
106
-
107
- module FakePool
108
- def self.wait
109
- end
110
-
111
- def self.run(*arguments)
112
- 0
113
- end
114
- end
115
- end
116
- end