build-graph 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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