@@ -7,78 +7,183 @@ import (
77
88 "github.com/paketo-buildpacks/packit"
99 "github.com/paketo-buildpacks/packit/chronos"
10+ "github.com/paketo-buildpacks/packit/fs"
1011)
1112
1213//go:generate faux --interface InstallProcess --output fakes/install_process.go
14+ //go:generate faux --interface EntryResolver --output fakes/entry_resolver.go
15+
16+ // InstallProcess defines the interface for executing the "bundle install"
17+ // build process.
1318type InstallProcess interface {
14- ShouldRun (layer packit. Layer , workingDir string ) (should bool , checksum string , rubyVersion string , err error )
19+ ShouldRun (metadata map [ string ] interface {} , workingDir string ) (should bool , checksum string , rubyVersion string , err error )
1520 Execute (workingDir , layerPath string , config map [string ]string ) error
1621}
1722
18- //go:generate faux --interface EntryResolver --output fakes/entry_resolver.go
23+ // EntryResolver defines the interface for determining what phases of the
24+ // lifecycle will require gems.
1925type EntryResolver interface {
2026 MergeLayerTypes (string , []packit.BuildpackPlanEntry ) (launch , build bool )
2127}
2228
29+ // Build will return a packit.BuildFunc that will be invoked during the build
30+ // phase of the buildpack lifecycle.
31+ //
32+ // Build will execute the installation process to install gems that can be used
33+ // in the build or launch phases of the buildpack lifecycle. Specifically,
34+ // Build will provide different sets of gems as discrete layers depending upon
35+ // their requirement in either the build or launch phase.
36+ //
37+ // If gems are required during the build phase, Build will ensure that all
38+ // gems, including those in the "development" and "test" groups are installed
39+ // into a layer that is made available during the remainder of the build phase.
40+ //
41+ // If gems are required during the launch phase, Build will ensure that only
42+ // those gems that are not in the "development" or "test" groups are installed
43+ // into a layer that is made available during the launch phase.
44+ //
45+ // If gems are required during both the build and launch phases, Build will
46+ // provide both of the above layers with their sets of gems. These layers
47+ // operate mutually exclusively as only one is available in each of the build
48+ // or launch phase.
49+ //
50+ // To improve performance when installing gems for use in both the build and
51+ // launch phases, Build will copy the contents of the build layer into the
52+ // launch layer before executing the launch layer installation process. This
53+ // will result in the launch layer installation process performing an effective
54+ // "no-op" as all of the gems that it requires should already be copied into
55+ // the layer. The launch layer installation process will however perform a
56+ // "bundle clean" to remove any extra gems, including those from the
57+ // "development" and "test" groups that may have been copied from the build
58+ // layer.
59+ //
60+ // Finally, upon completing the installation process, Build will remove any
61+ // local bundler configuration files such that the Bundler CLI will only follow
62+ // configuration from the global location, which will be configured to point to
63+ // a file that is maintained in each of the build and launch layers
64+ // respectively.
2365func Build (installProcess InstallProcess , logger LogEmitter , clock chronos.Clock , entries EntryResolver ) packit.BuildFunc {
2466 return func (context packit.BuildContext ) (packit.BuildResult , error ) {
2567 logger .Title ("%s %s" , context .BuildpackInfo .Name , context .BuildpackInfo .Version )
2668
27- gemsLayer , err := context .Layers .Get (LayerNameGems )
28- if err != nil {
29- return packit.BuildResult {}, err
30- }
69+ launch , build := entries .MergeLayerTypes ("gems" , context .Plan .Entries )
3170
32- should , sum , rubyVersion , err := installProcess .ShouldRun (gemsLayer , context .WorkingDir )
33- if err != nil {
34- return packit.BuildResult {}, err
35- }
71+ var layers []packit.Layer
72+
73+ if build {
74+ layer , err := context .Layers .Get (LayerNameBuildGems )
75+ if err != nil {
76+ return packit.BuildResult {}, err
77+ }
3678
37- if ! should {
38- logger .Process ("Reusing cached layer %s" , gemsLayer .Path )
39- logger .Break ()
79+ layer .Build = true
80+ layer .Cache = true
4081
41- err := os . RemoveAll ( filepath . Join ( context . WorkingDir , ".bundle" , "config" ) )
82+ should , checksum , rubyVersion , err := installProcess . ShouldRun ( layer . Metadata , context . WorkingDir )
4283 if err != nil {
4384 return packit.BuildResult {}, err
4485 }
4586
46- return packit.BuildResult {Layers : []packit.Layer {gemsLayer }}, nil
87+ if should {
88+ logger .Process ("Executing build environment install process" )
89+
90+ duration , err := clock .Measure (func () error {
91+ return installProcess .Execute (context .WorkingDir , layer .Path , map [string ]string {
92+ "path" : layer .Path ,
93+ "clean" : "true" ,
94+ })
95+ })
96+ if err != nil {
97+ return packit.BuildResult {}, err
98+ }
99+
100+ logger .Action ("Completed in %s" , duration .Round (time .Millisecond ))
101+ logger .Break ()
102+
103+ layer .BuildEnv .Default ("BUNDLE_USER_CONFIG" , filepath .Join (layer .Path , "config" ))
104+ layer .Metadata = map [string ]interface {}{
105+ "built_at" : clock .Now ().Format (time .RFC3339Nano ),
106+ "cache_sha" : checksum ,
107+ "ruby_version" : rubyVersion ,
108+ }
109+ } else {
110+ logger .Process ("Reusing cached layer %s" , layer .Path )
111+ logger .Break ()
112+ }
113+
114+ layers = append (layers , layer )
47115 }
48116
49- logger .Process ("Executing build process" )
117+ if launch {
118+ layer , err := context .Layers .Get (LayerNameLaunchGems )
119+ if err != nil {
120+ return packit.BuildResult {}, err
121+ }
50122
51- duration , err := clock .Measure (func () error {
52- err := installProcess .Execute (context .WorkingDir , gemsLayer .Path , map [string ]string {
53- "path" : gemsLayer .Path ,
54- "without" : "development:test" ,
55- "clean" : "true" ,
56- })
123+ layer .Launch = true
124+
125+ should , checksum , rubyVersion , err := installProcess .ShouldRun (layer .Metadata , context .WorkingDir )
57126 if err != nil {
58- return err
127+ return packit. BuildResult {}, err
59128 }
60129
61- return os .RemoveAll (filepath .Join (context .WorkingDir , ".bundle" , "config" ))
62- })
63- if err != nil {
64- return packit.BuildResult {}, err
65- }
130+ if should {
131+ logger .Process ("Executing launch environment install process" )
132+
133+ duration , err := clock .Measure (func () error {
134+ if build {
135+ buildLayer , err := context .Layers .Get (LayerNameBuildGems )
136+ if err != nil {
137+ return err
138+ }
139+
140+ err = fs .Copy (filepath .Join (buildLayer .Path ), filepath .Join (layer .Path ))
141+ if err != nil {
142+ return err
143+ }
144+ }
145+
146+ return installProcess .Execute (context .WorkingDir , layer .Path , map [string ]string {
147+ "path" : layer .Path ,
148+ "without" : "development:test" ,
149+ "clean" : "true" ,
150+ })
151+ })
152+ if err != nil {
153+ return packit.BuildResult {}, err
154+ }
155+
156+ logger .Action ("Completed in %s" , duration .Round (time .Millisecond ))
157+ logger .Break ()
158+
159+ layer .LaunchEnv .Default ("BUNDLE_USER_CONFIG" , filepath .Join (layer .Path , "config" ))
160+ layer .Metadata = map [string ]interface {}{
161+ "built_at" : clock .Now ().Format (time .RFC3339Nano ),
162+ "cache_sha" : checksum ,
163+ "ruby_version" : rubyVersion ,
164+ }
165+ } else {
166+ logger .Process ("Reusing cached layer %s" , layer .Path )
167+ logger .Break ()
168+ }
66169
67- logger . Action ( "Completed in %s" , duration . Round ( time . Millisecond ) )
68- logger . Break ()
170+ layers = append ( layers , layer )
171+ }
69172
70- gemsLayer .Launch , gemsLayer .Build = entries .MergeLayerTypes ("gems" , context .Plan .Entries )
71- gemsLayer .Cache = gemsLayer .Build
173+ for _ , layer := range layers {
174+ logger .Environment (layer )
175+ }
72176
73- gemsLayer .Metadata = map [string ]interface {}{
74- "built_at" : clock .Now ().Format (time .RFC3339Nano ),
75- "cache_sha" : sum ,
76- "ruby_version" : rubyVersion ,
177+ err := os .RemoveAll (filepath .Join (context .WorkingDir , ".bundle" , "config" ))
178+ if err != nil {
179+ return packit.BuildResult {}, err
77180 }
78181
79- gemsLayer .SharedEnv .Default ("BUNDLE_USER_CONFIG" , filepath .Join (gemsLayer .Path , "config" ))
80- logger .Environment (gemsLayer .SharedEnv )
182+ err = os .RemoveAll (filepath .Join (context .WorkingDir , ".bundle" , "config.bak" ))
183+ if err != nil {
184+ return packit.BuildResult {}, err
185+ }
81186
82- return packit.BuildResult {Layers : []packit. Layer { gemsLayer } }, nil
187+ return packit.BuildResult {Layers : layers }, nil
83188 }
84189}
0 commit comments