summaryrefslogtreecommitdiff
path: root/omnibus
diff options
context:
space:
mode:
authorJohn Keiser <john@johnkeiser.com>2016-04-11 13:52:52 -0700
committerJohn Keiser <john@johnkeiser.com>2016-04-18 14:21:02 -0700
commit612932e984e4a210891e5d2d00d25723afd6b6a4 (patch)
treed2c8b9f1eb3b8002f61dd4dc82f4ad7564f43dae /omnibus
parent257500a90a17e9604c798f2b73afd0ada5d42903 (diff)
downloadchef-612932e984e4a210891e5d2d00d25723afd6b6a4.tar.gz
Use locked dependencies to build chef
Diffstat (limited to 'omnibus')
-rw-r--r--omnibus/config/projects/chef.rb55
-rw-r--r--omnibus/config/software/chef-appbundle.rb14
-rw-r--r--omnibus/config/software/chef-complete.rb20
-rw-r--r--omnibus/config/software/chef-gem-binding_of_caller.rb6
-rw-r--r--omnibus/config/software/chef-gem-byebug.rb6
-rw-r--r--omnibus/config/software/chef-gem-debug_inspector.rb6
-rw-r--r--omnibus/config/software/chef-gem-ffi-yajl.rb8
-rw-r--r--omnibus/config/software/chef-gem-ffi.rb6
-rw-r--r--omnibus/config/software/chef-gem-json.rb6
-rw-r--r--omnibus/config/software/chef-gem-libyajl2.rb6
-rw-r--r--omnibus/config/software/chef-gem-mini_portile2.rb6
-rw-r--r--omnibus/config/software/chef-gem-nokogiri.rb8
-rw-r--r--omnibus/config/software/chef-gem-ruby-prof.rb6
-rw-r--r--omnibus/config/software/chef-gem-ruby-shadow.rb6
-rw-r--r--omnibus/config/software/chef-remove-docs.rb33
-rw-r--r--omnibus/config/software/chef.rb90
-rw-r--r--omnibus/files/chef-appbundle/build-chef-appbundle.rb93
-rw-r--r--omnibus/files/chef-gem/build-chef-gem.rb123
-rw-r--r--omnibus/files/chef-gem/build-chef-gem/gem-install-software-def.rb132
-rw-r--r--omnibus/files/chef/build-chef.rb146
20 files changed, 728 insertions, 48 deletions
diff --git a/omnibus/config/projects/chef.rb b/omnibus/config/projects/chef.rb
index aec14bfe0e..ce79c5cd1d 100644
--- a/omnibus/config/projects/chef.rb
+++ b/omnibus/config/projects/chef.rb
@@ -36,61 +36,20 @@ else
install_dir "#{default_root}/#{name}"
end
-override :ruby, version: "2.1.8"
-# Leave dev-kit pinned to 4.5 because 4.7 is 20MB larger and we don't want
-# to unnecessarily make the client any fatter.
-override :'ruby-windows-devkit', version: "4.5.2-20111229-1559" if windows? && windows_arch_i386?
-override :bundler, version: "1.11.2"
-override :rubygems, version: "2.5.2"
-
-# Chef Release version pinning
-override :chef, version: "local_source"
-override :ohai, version: "master"
-
# Global FIPS override flag.
if windows? || rhel?
override :fips, enabled: true
end
-dependency "preparation"
-dependency "rb-readline"
-dependency "nokogiri"
-dependency "pry"
-dependency "chef"
-dependency "shebang-cleanup"
-dependency "version-manifest"
-dependency "openssl-customization"
-
-if windows?
- dependency "ruby-windows-devkit"
- dependency "ruby-windows-devkit-bash"
-end
-
-# Lower level library pins
-override :xproto, version: "7.0.28"
-override :"util-macros", version: "1.19.0"
-override :makedepend, version: "1.0.5"
-
-## We are currently on the latest of these:
-#override :"ncurses", version: "5.9"
-#override :"zlib", version: "1.2.8"
-#override :"pkg-config-lite", version: "0.28-1"
-#override :"libffi", version: "3.2.1"
-#override :"libyaml", version: "0.1.6"
-#override :"libiconv", version: "1.14"
-#override :"liblzma", version: "5.2.2"
-#override :"libxml2", version: "2.9.3"
-#override :"libxslt", version: "1.1.28"
+# Load dynamically updated overrides
+overrides_path = File.expand_path("../../../../omnibus_overrides.rb", __FILE__)
+instance_eval(IO.read(overrides_path), overrides_path)
-## according to comment in omnibus-sw, latest versions don't work on solaris
-# https://github.com/chef/omnibus-software/blob/aefb7e79d29ca746c3f843673ef5e317fa3cba54/config/software/libtool.rb#L23
-#override :"libtool"
-
-## These can float as they are frequently updated in a way that works for us
-#override :"cacerts", # probably best to float?
-#override :"openssl" # leave this?
+dependency "preparation"
-dependency "clean-static-libs"
+# All actual dependencies are in chef-complete, so that the addition
+# or removal of a dependency doesn't dirty the entire project file
+dependency "chef-complete"
package :rpm do
signing_passphrase ENV["OMNIBUS_RPM_SIGNING_PASSPHRASE"]
diff --git a/omnibus/config/software/chef-appbundle.rb b/omnibus/config/software/chef-appbundle.rb
new file mode 100644
index 0000000000..19228738c3
--- /dev/null
+++ b/omnibus/config/software/chef-appbundle.rb
@@ -0,0 +1,14 @@
+name "chef-appbundle"
+default_version "local_source"
+source path: project.files_path
+
+dependency "chef"
+
+build do
+ # This is where we get the definitions below
+ require_relative "../../files/chef-appbundle/build-chef-appbundle"
+ extend BuildChefAppbundle
+
+ appbundle_gem "chef"
+ appbundle_gem "ohai"
+end
diff --git a/omnibus/config/software/chef-complete.rb b/omnibus/config/software/chef-complete.rb
new file mode 100644
index 0000000000..46fc8046e4
--- /dev/null
+++ b/omnibus/config/software/chef-complete.rb
@@ -0,0 +1,20 @@
+name "chef-complete"
+
+license :project_license
+
+dependency "chef"
+dependency "chef-appbundle"
+dependency "chef-remove-docs"
+
+dependency "shebang-cleanup"
+dependency "version-manifest"
+dependency "openssl-customization"
+
+if windows?
+ # TODO can this be safely moved to before the chef?
+ # It would make caching better ...
+ dependency "ruby-windows-devkit"
+ dependency "ruby-windows-devkit-bash"
+end
+
+dependency "clean-static-libs"
diff --git a/omnibus/config/software/chef-gem-binding_of_caller.rb b/omnibus/config/software/chef-gem-binding_of_caller.rb
new file mode 100644
index 0000000000..a9f8b758c9
--- /dev/null
+++ b/omnibus/config/software/chef-gem-binding_of_caller.rb
@@ -0,0 +1,6 @@
+# gem installs this gem from the version specified in chef's Gemfile.lock
+# so we can take advantage of omnibus's caching. Just duplicate this file and
+# add the new software def to chef software def if you want to separate
+# another gem's installation.
+require_relative "../../files/chef-gem/build-chef-gem/gem-install-software-def"
+BuildChefGem::GemInstallSoftwareDef.define(self, __FILE__)
diff --git a/omnibus/config/software/chef-gem-byebug.rb b/omnibus/config/software/chef-gem-byebug.rb
new file mode 100644
index 0000000000..a9f8b758c9
--- /dev/null
+++ b/omnibus/config/software/chef-gem-byebug.rb
@@ -0,0 +1,6 @@
+# gem installs this gem from the version specified in chef's Gemfile.lock
+# so we can take advantage of omnibus's caching. Just duplicate this file and
+# add the new software def to chef software def if you want to separate
+# another gem's installation.
+require_relative "../../files/chef-gem/build-chef-gem/gem-install-software-def"
+BuildChefGem::GemInstallSoftwareDef.define(self, __FILE__)
diff --git a/omnibus/config/software/chef-gem-debug_inspector.rb b/omnibus/config/software/chef-gem-debug_inspector.rb
new file mode 100644
index 0000000000..a9f8b758c9
--- /dev/null
+++ b/omnibus/config/software/chef-gem-debug_inspector.rb
@@ -0,0 +1,6 @@
+# gem installs this gem from the version specified in chef's Gemfile.lock
+# so we can take advantage of omnibus's caching. Just duplicate this file and
+# add the new software def to chef software def if you want to separate
+# another gem's installation.
+require_relative "../../files/chef-gem/build-chef-gem/gem-install-software-def"
+BuildChefGem::GemInstallSoftwareDef.define(self, __FILE__)
diff --git a/omnibus/config/software/chef-gem-ffi-yajl.rb b/omnibus/config/software/chef-gem-ffi-yajl.rb
new file mode 100644
index 0000000000..e8279f7d81
--- /dev/null
+++ b/omnibus/config/software/chef-gem-ffi-yajl.rb
@@ -0,0 +1,8 @@
+# gem installs this gem from the version specified in chef's Gemfile.lock
+# so we can take advantage of omnibus's caching. Just duplicate this file and
+# add the new software def to chef software def if you want to separate
+# another gem's installation.
+require_relative "../../files/chef-gem/build-chef-gem/gem-install-software-def"
+BuildChefGem::GemInstallSoftwareDef.define(self, __FILE__)
+
+dependency "chef-gem-libyajl2"
diff --git a/omnibus/config/software/chef-gem-ffi.rb b/omnibus/config/software/chef-gem-ffi.rb
new file mode 100644
index 0000000000..a9f8b758c9
--- /dev/null
+++ b/omnibus/config/software/chef-gem-ffi.rb
@@ -0,0 +1,6 @@
+# gem installs this gem from the version specified in chef's Gemfile.lock
+# so we can take advantage of omnibus's caching. Just duplicate this file and
+# add the new software def to chef software def if you want to separate
+# another gem's installation.
+require_relative "../../files/chef-gem/build-chef-gem/gem-install-software-def"
+BuildChefGem::GemInstallSoftwareDef.define(self, __FILE__)
diff --git a/omnibus/config/software/chef-gem-json.rb b/omnibus/config/software/chef-gem-json.rb
new file mode 100644
index 0000000000..a9f8b758c9
--- /dev/null
+++ b/omnibus/config/software/chef-gem-json.rb
@@ -0,0 +1,6 @@
+# gem installs this gem from the version specified in chef's Gemfile.lock
+# so we can take advantage of omnibus's caching. Just duplicate this file and
+# add the new software def to chef software def if you want to separate
+# another gem's installation.
+require_relative "../../files/chef-gem/build-chef-gem/gem-install-software-def"
+BuildChefGem::GemInstallSoftwareDef.define(self, __FILE__)
diff --git a/omnibus/config/software/chef-gem-libyajl2.rb b/omnibus/config/software/chef-gem-libyajl2.rb
new file mode 100644
index 0000000000..a9f8b758c9
--- /dev/null
+++ b/omnibus/config/software/chef-gem-libyajl2.rb
@@ -0,0 +1,6 @@
+# gem installs this gem from the version specified in chef's Gemfile.lock
+# so we can take advantage of omnibus's caching. Just duplicate this file and
+# add the new software def to chef software def if you want to separate
+# another gem's installation.
+require_relative "../../files/chef-gem/build-chef-gem/gem-install-software-def"
+BuildChefGem::GemInstallSoftwareDef.define(self, __FILE__)
diff --git a/omnibus/config/software/chef-gem-mini_portile2.rb b/omnibus/config/software/chef-gem-mini_portile2.rb
new file mode 100644
index 0000000000..a9f8b758c9
--- /dev/null
+++ b/omnibus/config/software/chef-gem-mini_portile2.rb
@@ -0,0 +1,6 @@
+# gem installs this gem from the version specified in chef's Gemfile.lock
+# so we can take advantage of omnibus's caching. Just duplicate this file and
+# add the new software def to chef software def if you want to separate
+# another gem's installation.
+require_relative "../../files/chef-gem/build-chef-gem/gem-install-software-def"
+BuildChefGem::GemInstallSoftwareDef.define(self, __FILE__)
diff --git a/omnibus/config/software/chef-gem-nokogiri.rb b/omnibus/config/software/chef-gem-nokogiri.rb
new file mode 100644
index 0000000000..a18d156e11
--- /dev/null
+++ b/omnibus/config/software/chef-gem-nokogiri.rb
@@ -0,0 +1,8 @@
+# gem installs this gem from the version specified in chef's Gemfile.lock
+# so we can take advantage of omnibus's caching. Just duplicate this file and
+# add the new software def to chef software def if you want to separate
+# another gem's installation.
+require_relative "../../files/chef-gem/build-chef-gem/gem-install-software-def"
+BuildChefGem::GemInstallSoftwareDef.define(self, __FILE__)
+
+dependency "chef-gem-mini_portile2"
diff --git a/omnibus/config/software/chef-gem-ruby-prof.rb b/omnibus/config/software/chef-gem-ruby-prof.rb
new file mode 100644
index 0000000000..a9f8b758c9
--- /dev/null
+++ b/omnibus/config/software/chef-gem-ruby-prof.rb
@@ -0,0 +1,6 @@
+# gem installs this gem from the version specified in chef's Gemfile.lock
+# so we can take advantage of omnibus's caching. Just duplicate this file and
+# add the new software def to chef software def if you want to separate
+# another gem's installation.
+require_relative "../../files/chef-gem/build-chef-gem/gem-install-software-def"
+BuildChefGem::GemInstallSoftwareDef.define(self, __FILE__)
diff --git a/omnibus/config/software/chef-gem-ruby-shadow.rb b/omnibus/config/software/chef-gem-ruby-shadow.rb
new file mode 100644
index 0000000000..a9f8b758c9
--- /dev/null
+++ b/omnibus/config/software/chef-gem-ruby-shadow.rb
@@ -0,0 +1,6 @@
+# gem installs this gem from the version specified in chef's Gemfile.lock
+# so we can take advantage of omnibus's caching. Just duplicate this file and
+# add the new software def to chef software def if you want to separate
+# another gem's installation.
+require_relative "../../files/chef-gem/build-chef-gem/gem-install-software-def"
+BuildChefGem::GemInstallSoftwareDef.define(self, __FILE__)
diff --git a/omnibus/config/software/chef-remove-docs.rb b/omnibus/config/software/chef-remove-docs.rb
new file mode 100644
index 0000000000..2e71e63792
--- /dev/null
+++ b/omnibus/config/software/chef-remove-docs.rb
@@ -0,0 +1,33 @@
+#
+# Copyright 2012-2014 Chef Software, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+name "chef-remove-docs"
+
+license :project_license
+
+build do
+ # This is where we get the definitions below
+ require_relative "../../files/chef/build-chef"
+ extend BuildChef
+
+ delete "#{install_dir}/embedded/docs"
+ delete "#{install_dir}/embedded/share/man"
+ delete "#{install_dir}/embedded/share/doc"
+ delete "#{install_dir}/embedded/share/gtk-doc"
+ delete "#{install_dir}/embedded/ssl/man"
+ delete "#{install_dir}/embedded/man"
+ delete "#{install_dir}/embedded/info"
+end
diff --git a/omnibus/config/software/chef.rb b/omnibus/config/software/chef.rb
new file mode 100644
index 0000000000..02f4162313
--- /dev/null
+++ b/omnibus/config/software/chef.rb
@@ -0,0 +1,90 @@
+name "chef"
+default_version "local_source"
+
+license :project_license
+
+# For the specific super-special version "local_source", build the source from
+# the local git checkout. This is what you'd want to occur by default if you
+# just ran omnibus build locally.
+version("local_source") do
+ source path: File.expand_path("../..", project.files_path),
+ # Since we are using the local repo, we try to not copy any files
+ # that are generated in the process of bundle installing omnibus.
+ # If the install steps are well-behaved, this should not matter
+ # since we only perform bundle and gem installs from the
+ # omnibus cache source directory, but we do this regardless
+ # to maintain consistency between what a local build sees and
+ # what a github based build will see.
+ options: { exclude: [ "omnibus/vendor" ] }
+end
+
+# For any version other than "local_source", fetch from github.
+if version != "local_source"
+ source git: "git://github.com/chef/chef.git"
+end
+
+# For nokogiri
+dependency "libxml2"
+dependency "libxslt"
+dependency "libiconv"
+dependency "liblzma"
+dependency "zlib"
+
+# ruby and bundler and friends
+dependency "ruby"
+dependency "rubygems"
+dependency "bundler"
+
+# Install all the native gems separately
+# Worst offenders first to take best advantage of cache:
+dependency "chef-gem-ffi-yajl"
+dependency "chef-gem-nokogiri"
+dependency "chef-gem-libyajl2"
+dependency "chef-gem-ruby-prof"
+dependency "chef-gem-byebug"
+dependency "chef-gem-debug_inspector"
+dependency "chef-gem-binding_of_caller"
+
+# Now everyone else, in alphabetical order because we don't care THAT much
+Dir.entries(File.dirname(__FILE__)).sort.each do |gem_software|
+ if gem_software =~ /^(chef-gem-.+)\.rb$/
+ dependency $1
+ end
+end
+
+build do
+ # This is where we get the definitions below
+ require_relative "../../files/chef/build-chef"
+ extend BuildChef
+
+ chef_build_env = env.dup
+ chef_build_env["BUNDLE_GEMFILE"] = chef_gemfile
+
+ # Prepare to install: build config, retries, job, frozen=true
+ # TODO Windows install seems to sometimes install already-installed gems such
+ # as gherkin (and fail as a result) if you use jobs: 4.
+ create_bundle_config(chef_gemfile, retries: 4, jobs: 1, frozen: true)
+
+ # Install all the things. Arguments are specified in .bundle/config (see create_bundle_config)
+ block { log.info(log_key) { "" } }
+ bundle "install --verbose", env: chef_build_env
+
+ # For whatever reason, nokogiri software def deletes this (rather small) directory
+ block { log.info(log_key) { "" } }
+ block "Remove mini_portile test dir" do
+ mini_portile = shellout!("#{bundle_bin} show mini_portile").stdout.chomp
+ remove_directory File.join(mini_portile, "test")
+ end
+
+ # Check that it worked
+ block { log.info(log_key) { "" } }
+ bundle "check", env: chef_build_env
+
+ # fix up git-sourced gems
+ properly_reinstall_git_and_path_sourced_gems
+ install_shared_gemfile
+
+ # Check that the final gemfile worked
+ block { log.info(log_key) { "" } }
+ bundle "check", env: env, cwd: File.dirname(shared_gemfile)
+end
diff --git a/omnibus/files/chef-appbundle/build-chef-appbundle.rb b/omnibus/files/chef-appbundle/build-chef-appbundle.rb
new file mode 100644
index 0000000000..97a94ef5a7
--- /dev/null
+++ b/omnibus/files/chef-appbundle/build-chef-appbundle.rb
@@ -0,0 +1,93 @@
+require_relative "../chef-gem/build-chef-gem"
+
+module BuildChefAppbundle
+ include BuildChefGem
+
+ def lockdown_gem(gem_name)
+ shared_gemfile = self.shared_gemfile
+
+ # Update the Gemfile to restrict to built versions so that bundle installs
+ # will do the right thing
+ block "Lock down the #{gem_name} gem" do
+ installed_path = shellout!("#{bundle_bin} show #{gem_name}", env: env, cwd: install_dir).stdout.chomp
+ installed_gemfile = File.join(installed_path, "Gemfile")
+
+ #
+ # Include the main distribution Gemfile in the gem's Gemfile
+ #
+ # NOTE: if this fails and the build retries, you will see this multiple
+ # times in the file.
+ #
+ distribution_gemfile = Pathname(shared_gemfile).relative_path_from(Pathname(installed_gemfile)).to_s
+ gemfile_text = IO.read(installed_gemfile)
+ gemfile_text << <<-EOM.gsub(/^\s+/, "")
+ # Lock gems that are part of the distribution
+ distribution_gemfile = File.expand_path(#{distribution_gemfile.inspect}, __FILE__)
+ instance_eval(IO.read(distribution_gemfile), distribution_gemfile)
+ EOM
+ create_file(installed_gemfile) { gemfile_text }
+
+ # Remove the gemfile.lock
+ remove_file("#{installed_gemfile}.lock") if File.exist?("#{installed_gemfile}.lock")
+
+ # If it's frozen, make it not be.
+ shellout!("#{bundle_bin} config --delete frozen")
+
+ # This could be changed to `bundle install` if we wanted to actually
+ # install extra deps out of their gemfile ...
+ shellout!("#{bundle_bin} lock", env: env, cwd: installed_path)
+ # bundle lock doesn't always tell us when it fails, so we have to check :/
+ unless File.exist?("#{installed_gemfile}.lock")
+ raise "bundle lock failed: no #{installed_gemfile}.lock created!"
+ end
+
+ # Ensure all the gems we need are actually installed (if the bundle adds
+ # something, we need to know about it so we can include it in the main
+ # solve).
+ # Save bundle config and modify to use --without development before checking
+ bundle_config = File.expand_path("../.bundle/config", installed_gemfile)
+ orig_config = IO.read(bundle_config) if File.exist?(bundle_config)
+ # "test", "changelog" and "guard" come from berkshelf, "maintenance" comes from chef
+ # "tools" and "integration" come from inspec
+ shellout!("#{bundle_bin} config --local without #{without_groups.join(":")}", env: env, cwd: installed_path)
+ shellout!("#{bundle_bin} config --local frozen 1")
+
+ shellout!("#{bundle_bin} check", env: env, cwd: installed_path)
+
+ # Restore bundle config
+ if orig_config
+ create_file(bundle_config) { orig_config }
+ else
+ remove_file bundle_config
+ end
+ end
+ end
+
+ # appbundle the gem, making /opt/chef/bin/<binary> do the superfast pinning
+ # thing.
+ #
+ # To protect the app from loading the wrong versions of things, it uses
+ # appbundler against the resulting file.
+ #
+ # Relocks the Gemfiles inside the specified gems (e.g. berkshelf, test-kitchen,
+ # chef) to use the chef distribution's chosen gems.
+ def appbundle_gem(gem_name)
+ # First lock the gemfile down.
+ lockdown_gem(gem_name)
+
+ shared_gemfile = self.shared_gemfile
+
+ # Ensure the main bin dir exists
+ bin_dir = File.join(install_dir, "bin")
+ mkdir(bin_dir)
+
+ block "Lock down the #{gem_name} gem" do
+ installed_path = shellout!("#{bundle_bin} show #{gem_name}", env: env, cwd: install_dir).stdout.chomp
+
+ # appbundle the gem
+ appbundler_args = [ installed_path, bin_dir, gem_name ]
+ appbundler_args = appbundler_args.map { |a| ::Shellwords.escape(a) }
+ shellout!("#{appbundler_bin} #{appbundler_args.join(" ")}", env: env, cwd: installed_path)
+ end
+ end
+end
diff --git a/omnibus/files/chef-gem/build-chef-gem.rb b/omnibus/files/chef-gem/build-chef-gem.rb
new file mode 100644
index 0000000000..82c0ba08e2
--- /dev/null
+++ b/omnibus/files/chef-gem/build-chef-gem.rb
@@ -0,0 +1,123 @@
+require "shellwords"
+require "pathname"
+require "bundler"
+require_relative "../../../version_policy"
+
+# Common definitions and helpers (like compile environment and binary
+# locations) for all chef software definitions.
+module BuildChefGem
+ PLATFORM_FAMILY_FAMILIES = {
+ "linux" => %w{wrlinux debian fedora rhel suse gentoo slackware arch exherbo alpine},
+ "bsd" => %w{dragonflybsd freebsd netbsd openbsd},
+ "solaris" => %w{smartos omnios openindiana opensolaris solaris2 nextentacore},
+ "aix" => %w{aix},
+ "windows" => %w{windows},
+ "mac_os_x" => %w{mac_os_x},
+ }
+ def platform_family_families
+ PLATFORM_FAMILY_FAMILIES.keys
+ end
+
+ def platform_family_family
+ PLATFORM_FAMILY_FAMILIES.
+ select { |key, families| families.include?(Omnibus::Ohai["platform_family"]) }.
+ first[0]
+ end
+
+ def embedded_bin(binary)
+ windows_safe_path("#{install_dir}/embedded/bin/#{binary}")
+ end
+
+ def appbundler_bin
+ embedded_bin("appbundler")
+ end
+
+ def bundle_bin
+ embedded_bin("bundle")
+ end
+
+ def gem_bin
+ embedded_bin("gem")
+ end
+
+ def rake_bin
+ embedded_bin("rake")
+ end
+
+ def without_groups
+ # Add --without for every known OS except the one we're in.
+ exclude_os_groups = platform_family_families - [ platform_family_family ]
+ (INSTALL_WITHOUT_GROUPS + exclude_os_groups).map { |g| g.to_sym }
+ end
+
+ #
+ # Get the path to the top level shared Gemfile included by all individual
+ # Gemfiles
+ #
+ def shared_gemfile
+ File.join(install_dir, "Gemfile")
+ end
+
+ # A common env for building everything including nokogiri and dep-selector-libgecode
+ def env
+ env = with_standard_compiler_flags(with_embedded_path, bfd_flags: true)
+
+ # From dep-selector-libgecode
+ # On some RHEL-based systems, the default GCC that's installed is 4.1. We
+ # need to use 4.4, which is provided by the gcc44 and gcc44-c++ packages.
+ # These do not use the gcc binaries so we set the flags to point to the
+ # correct version here.
+ if File.exist?("/usr/bin/gcc44")
+ env["CC"] = "gcc44"
+ env["CXX"] = "g++44"
+ end
+
+ # From dep-selector-libgecode
+ # Ruby DevKit ships with BSD Tar
+ env["PROG_TAR"] = "bsdtar" if windows?
+ env["ARFLAGS"] = "rv #{env["ARFLAGS"]}" if env["ARFLAGS"]
+
+ # Set up nokogiri environment and args
+ env["NOKOGIRI_USE_SYSTEM_LIBRARIES"] = "true"
+ env
+ end
+
+ #
+ # Install arguments for various gems (to be passed to `gem install` or set in
+ # `bundle config build.<gemname>`).
+ #
+ def all_install_args
+ @all_install_args = {
+ "nokogiri" => %W{
+ --use-system-libraries
+ --with-xml2-lib=#{Shellwords.escape("#{install_dir}/embedded/lib")}
+ --with-xml2-include=#{Shellwords.escape("#{install_dir}/embedded/include/libxml2")}
+ --with-xslt-lib=#{Shellwords.escape("#{install_dir}/embedded/lib")}
+ --with-xslt-include=#{Shellwords.escape("#{install_dir}/embedded/include/libxslt")}
+ --with-iconv-dir=#{Shellwords.escape("#{install_dir}/embedded")}
+ --with-zlib-dir=#{Shellwords.escape("#{install_dir}/embedded")}
+ }.join(" "),
+ }
+ end
+
+ # gem install arguments for a particular gem. "" if no special args.
+ def install_args_for(gem_name)
+ all_install_args[gem_name] || ""
+ end
+
+ # Give block all the variables
+ def block(*args, &block)
+ super do
+ extend BuildChefGem
+ instance_eval(&block)
+ end
+ end
+
+ # Give build all the variables
+ def build(*args, &block)
+ super do
+ extend BuildChefGem
+ instance_eval(&block)
+ end
+ end
+end
diff --git a/omnibus/files/chef-gem/build-chef-gem/gem-install-software-def.rb b/omnibus/files/chef-gem/build-chef-gem/gem-install-software-def.rb
new file mode 100644
index 0000000000..5992ae8057
--- /dev/null
+++ b/omnibus/files/chef-gem/build-chef-gem/gem-install-software-def.rb
@@ -0,0 +1,132 @@
+require "bundler"
+require "omnibus"
+require_relative "../build-chef-gem"
+
+module BuildChefGem
+ class GemInstallSoftwareDef
+ def self.define(software, software_filename)
+ new(software, software_filename).send(:define)
+ end
+
+ include BuildChefGem
+ include Omnibus::Logging
+
+ protected
+
+ def initialize(software, software_filename)
+ @software = software
+ @software_filename = software_filename
+ end
+
+ attr_reader :software, :software_filename
+
+ def define
+ software.name "#{File.basename(software_filename)[0..-4]}"
+ software.default_version gem_version
+
+ # If the source directory for building stuff changes, tell omnibus to
+ # de-cache us
+ software.source path: File.expand_path("../..", __FILE__)
+
+ # ruby and bundler and friends
+ software.dependency "ruby"
+ software.dependency "rubygems"
+
+ gem_name = self.gem_name
+ gem_version = self.gem_version
+ gemspec = self.gemspec
+ lockfile_path = self.lockfile_path
+
+ software.build do
+ extend BuildChefGem
+
+ if gem_version == "<skip>"
+ if gemspec
+ block do
+ log.info(log_key) { "#{gem_name} has source #{gemspec.source.name} in #{lockfile_path}. We only cache rubygems.org installs in omnibus to keep things simple. The chef step will build #{gem_name} ..." }
+ end
+ else
+ block do
+ log.info(log_key) { "#{gem_name} is not in the #{lockfile_path}. This can happen if your OS doesn't build it, or if chef no longer depends on it. Skipping ..." }
+ end
+ end
+ else
+ block do
+ log.info(log_key) { "Found version #{gem_version} of #{gem_name} in #{lockfile_path}. Building early to take advantage of omnibus caching ..." }
+ end
+ gem "install #{gem_name} -v #{gem_version} --no-doc --no-ri --ignore-dependencies --verbose -- #{install_args_for(gem_name)}", env: env
+ end
+ end
+ end
+
+ # Path above omnibus (where Gemfile is)
+ def root_path
+ File.expand_path("../../../../..", __FILE__)
+ end
+
+ def gemfile_path
+ # gemfile path could be relative to software filename (and often is)
+ @gemfile_path ||= begin
+ # Grab the version (and maybe source) from the lockfile so omnibus knows whether
+ # to toss the cache or not
+ gemfile_path = File.join(root_path, "Gemfile")
+ platform_gemfile_path = "#{gemfile_path}.#{Omnibus::Ohai["platform"]}"
+ if File.exist?(platform_gemfile_path)
+ gemfile_path = platform_gemfile_path
+ end
+ gemfile_path
+ end
+ end
+
+ def lockfile_path
+ @lockfile_path ||= "#{gemfile_path}.lock"
+ end
+
+ def gem_name
+ @gem_name ||= begin
+ # File must be named chef-<gemname>.rb
+ # Will look at chef/Gemfile.lock and install that version of the gem using "gem install"
+ # (and only that version)
+ if File.basename(software_filename) =~ /^chef-gem-(.+)\.rb$/
+ $1
+ else
+ raise "#{software_filename} must be named chef-<gemname>.rb to build a gem automatically"
+ end
+ end
+ end
+
+ def gemspec
+ @gemspec ||= begin
+ old_frozen = Bundler.settings[:frozen]
+ Bundler.settings[:frozen] = true
+ begin
+ bundle = Bundler::Definition.build(gemfile_path, lockfile_path, nil)
+ dependencies = bundle.dependencies.select { |d| (d.groups - without_groups).any? }
+ # This is sacrilege: figure out a way we can grab the list of dependencies *without*
+ # requiring everything to be installed or calling private methods ...
+ gemspec = bundle.resolve.for(bundle.send(:expand_dependencies, dependencies)).find { |s| s.name == gem_name }
+ if gemspec
+ log.info(software.name) { "Using #{gem_name} version #{gemspec.version} from #{gemfile_path}" }
+ elsif bundle.resolve.find { |s| s.name == gem_name }
+ log.info(software.name) { "#{gem_name} not loaded from #{gemfile_path}, skipping" }
+ else
+ raise "#{gem_name} not found in #{gemfile_path} or #{lockfile_path}"
+ end
+ gemspec
+ ensure
+ Bundler.settings[:frozen] = old_frozen
+ end
+ end
+ end
+
+ def gem_version
+ @gem_version ||= begin
+ if gemspec && gemspec.source.name == "rubygems repository https://rubygems.org/"
+ gemspec.version.to_s
+ else
+ "<skip>"
+ end
+ end
+ end
+ end
+end
diff --git a/omnibus/files/chef/build-chef.rb b/omnibus/files/chef/build-chef.rb
new file mode 100644
index 0000000000..d3e68d4e8a
--- /dev/null
+++ b/omnibus/files/chef/build-chef.rb
@@ -0,0 +1,146 @@
+require "shellwords"
+require "pathname"
+require "bundler"
+require_relative "../chef-gem/build-chef-gem"
+require_relative "../../../version_policy"
+
+# We use this to break up the `build` method into readable parts
+module BuildChef
+ include BuildChefGem
+
+ def create_bundle_config(gemfile, without: without_groups, retries: nil, jobs: nil, frozen: nil)
+ if without
+ without = without.dup
+ # no_aix, no_windows groups
+ without << "no_#{Omnibus::Ohai["platform"]}"
+ end
+
+ bundle_config = File.expand_path("../.bundle/config", gemfile)
+
+ block "Put build config into #{bundle_config}: #{ { without: without, retries: retries, jobs: jobs, frozen: frozen } }" do
+ # bundle config build.nokogiri #{nokogiri_build_config} messes up the line,
+ # so we write it directly ourselves.
+ new_bundle_config = "---\n"
+ new_bundle_config << "BUNDLE_WITHOUT: #{Array(without).join(":")}\n" if without
+ new_bundle_config << "BUNDLE_RETRY: #{retries}\n" if retries
+ new_bundle_config << "BUNDLE_JOBS: #{jobs}\n" if jobs
+ new_bundle_config << "BUNDLE_FROZEN: '1'\n" if frozen
+ all_install_args.each do |gem_name, install_args|
+ new_bundle_config << "BUNDLE_BUILD__#{gem_name.upcase}: #{install_args}\n"
+ end
+ create_file(bundle_config) { new_bundle_config }
+ end
+ end
+
+ #
+ # Get the (possibly platform-specific) path to the Gemfile.
+ # /var/omnibus/cache/src/chef/Gemfile or
+ # /var/omnibus/cache/src/chef/Gemfile.windows
+ #
+ def chef_gemfile
+ gemfile = File.join(project_dir, "Gemfile")
+ # Check for platform specific version
+ platform_gemfile = "#{gemfile}.#{Omnibus::Ohai["platform"]}"
+ if File.exist?(platform_gemfile)
+ gemfile = platform_gemfile
+ end
+ gemfile
+ end
+
+ #
+ # Some gems we installed don't end up in the `gem list` due to the fact that
+ # they have git sources (`gem 'chef', github: 'chef/chef'`) or paths (`gemspec`
+ # or `gem 'chef-config', path: 'chef-config'`). To get them in there, we need
+ # to go through these gems, run `rake install` from their top level, and
+ # then delete the git cached versions.
+ #
+ # Once we finish with all this, we update the Gemfile that will end up in the
+ # chef so that it doesn't have git or path references anymore.
+ #
+ def properly_reinstall_git_and_path_sourced_gems
+ # Emit blank line to separate different tasks
+ block { log.info(log_key) { "" } }
+ chef_env = env.dup.merge("BUNDLE_GEMFILE" => chef_gemfile)
+
+ # Reinstall git-sourced or path-sourced gems, and delete the originals
+ block "Reinstall git-sourced gems properly" do
+ # Grab info about the gem environment so we can make decisions
+ gemdir = shellout!("#{gem_bin} environment gemdir", env: env).stdout.chomp
+ gem_install_dir = File.join(gemdir, "gems")
+
+ # bundle list --paths gets us the list of gem install paths. Get the ones
+ # that are installed local (git and path sources like `gem :x, github: 'chef/x'`
+ # or `gem :x, path: '.'` or `gemspec`). To do this, we just detect which ones
+ # have properly-installed paths (in the `gems` directory that shows up when
+ # you run `gem list`).
+ locally_installed_gems = shellout!("#{bundle_bin} list --paths", env: chef_env, cwd: project_dir).
+ stdout.lines.select { |gem_path| !gem_path.start_with?(gem_install_dir) }
+
+ # Install the gems for real using `rake install` in their directories
+ locally_installed_gems.each do |gem_path|
+ gem_path = gem_path.chomp
+ # We use the already-installed bundle to rake install, because (hopefully)
+ # just rake installing doesn't require anything special.
+ # Emit blank line to separate different tasks
+ log.info(log_key) { "" }
+ log.info(log_key) { "Properly installing git or path sourced gem #{gem_path} using rake install" }
+ shellout!("#{bundle_bin} exec #{rake_bin} install", env: chef_env, cwd: gem_path)
+ end
+ end
+ end
+
+ def install_shared_gemfile
+ # Emit blank line to separate different tasks
+ block { log.info(log_key) { "" } }
+
+ shared_gemfile = self.shared_gemfile
+ chef_env = env.dup.merge("BUNDLE_GEMFILE" => chef_gemfile)
+
+ # Show the config for good measure
+ bundle "config", env: chef_env
+
+ # Make `Gemfile` point to these by removing path and git sources and pinning versions.
+ block "Rewrite Gemfile using all properly-installed gems" do
+ gem_pins = ""
+ result = []
+ shellout!("#{bundle_bin} list", env: chef_env).stdout.lines.map do |line|
+ if line =~ /^\s*\*\s*(\S+)\s+\((\S+).*\)\s*$/
+ name, version = $1, $2
+ # rubocop is an exception, since different projects disagree
+ next if GEMS_ALLOWED_TO_FLOAT.include?(name)
+ gem_pins << "override_gem #{name.inspect}, #{version.inspect}\n"
+ end
+ end
+
+ create_file(shared_gemfile) { <<-EOM }
+ # Meant to be included in component Gemfiles at the end with:
+ #
+ # instance_eval(IO.read("#{install_dir}/Gemfile"), "#{install_dir}/Gemfile")
+ #
+ # Override any existing gems with our own.
+ def override_gem(name, *args, &block)
+ # If the Gemfile re-specifies something in our lockfile, ignore it.
+ current = dependencies.find { |dep| dep.name == name }
+ dependencies.delete(current) if current
+ gem(name, *args, &block)
+ end
+ #{gem_pins}
+ EOM
+ end
+
+ shared_gemfile_env = env.dup.merge("BUNDLE_GEMFILE" => shared_gemfile)
+
+ # Create a `Gemfile.lock` at the final location
+ bundle "lock", env: shared_gemfile_env
+
+ # Freeze the location's Gemfile.lock.
+ create_bundle_config(shared_gemfile, frozen: true)
+
+ # Clear the now-unnecessary git caches, cached gems, and git-checked-out gems
+ block "Delete bundler git cache and git installs" do
+ gemdir = shellout!("#{gem_bin} environment gemdir", env: env).stdout.chomp
+ remove_file "#{gemdir}/cache"
+ remove_file "#{gemdir}/bundler"
+ end
+ end
+end