Skip to content

Commit

Permalink
Dep: First working version of LockfileUpdater
Browse files Browse the repository at this point in the history
  • Loading branch information
greysteil committed Jul 25, 2018
1 parent 8fb18c6 commit 4eb1515
Show file tree
Hide file tree
Showing 7 changed files with 230 additions and 8 deletions.
9 changes: 6 additions & 3 deletions lib/dependabot/file_updaters/go/dep.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module FileUpdaters
module Go
class Dep < Dependabot::FileUpdaters::Base
require_relative "dep/manifest_updater"
require_relative "dep/lockfile_updater"

def self.updated_files_regex
[
Expand Down Expand Up @@ -59,9 +60,11 @@ def updated_manifest_content
end

def updated_lockfile_content
# TODO: This normally needs to be written in the native language.
# We do so by shelling out to a helper method (see other languages)
lockfile.content
LockfileUpdater.new(
dependencies: dependencies,
dependency_files: dependency_files,
credentials: credentials
).updated_lockfile_content
end
end
end
Expand Down
187 changes: 187 additions & 0 deletions lib/dependabot/file_updaters/go/dep/lockfile_updater.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
# frozen_string_literal: true

require "toml-rb"

require "dependabot/shared_helpers"
require "dependabot/dependency_file"
require "dependabot/file_updaters/go/dep"
require "dependabot/file_parsers/go/dep"

module Dependabot
module FileUpdaters
module Go
class Dep
class LockfileUpdater
def initialize(dependencies:, dependency_files:, credentials:)
@dependencies = dependencies
@dependency_files = dependency_files
@credentials = credentials
end

def updated_lockfile_content
updated_content =
Dir.chdir(go_dir) do
write_temporary_dependency_files

SharedHelpers.with_git_configured(credentials: credentials) do
# Shell out to dep, which handles everything for us, and does
# so without doing an install (so it's fast).
command = "dep ensure -update --no-vendor "\
"#{dependencies.map(&:name).join(' ')}"
run_shell_command(command)
end

File.read("Gopkg.lock")
end

FileUtils.rm_rf(go_dir)
updated_content
end

private

attr_reader :dependencies, :dependency_files, :credentials

def run_shell_command(command)
raw_response = nil
IO.popen(command, err: %i(child out)) do |process|
raw_response = process.read
end

# Raise an error with the output from the shell session if dep
# returns a non-zero status
return if $CHILD_STATUS.success?
raise SharedHelpers::HelperSubprocessFailed.new(
raw_response,
command
)
end

def write_temporary_dependency_files
dependency_files.each do |file|
path = file.name
FileUtils.mkdir_p(Pathname.new(path).dirname)
File.write(file.name, file.content)
end

# Overwrite the manifest with our custom prepared one
File.write(prepared_manifest.name, prepared_manifest.content)

File.write("hello.go", dummy_app_content)
end

def prepared_manifest
DependencyFile.new(
name: manifest.name,
content: prepared_manifest_content
)
end

def prepared_manifest_content
parsed_manifest = TomlRB.parse(manifest.content)

dependencies.each do |dep|
req = dep.requirements.find { |r| r[:file] == manifest.name }

if req
update_constraint!(parsed_manifest, dep)
else
create_constraint!(parsed_manifest, dep)
end
end

TomlRB.dump(parsed_manifest)
end

# Used to lock the version when updating a top-level dependency
def update_constraint!(parsed_manifest, dep)
details =
parsed_manifest.
values_at(*FileParsers::Go::Dep::REQUIREMENT_TYPES).
flatten.compact.find { |d| d["name"] == dep.name }

req = dep.requirements.find { |r| r[:file] == manifest.name }

details.delete("branch")

if req.fetch(:source).fetch(:type) == "git"
details.delete("version")
details["revision"] = dep.version
else
details.delete("revision")
details["version"] = dep.version
end
end

# Used to lock the version when updating a subdependency
def create_constraint!(parsed_manifest, dep)
details = { "name" => dep.name }

# Fetch the details from the lockfile to check whether this
# sub-dependency needs a git revision or a version.
original_details =
parsed_file(lockfile).fetch("projects").
find { |p| p["name"] == dep.name }

if original_details["source"]
details["source"] = original_details["source"]
end

if original_details["version"]
details["version"] = dep.version
else
details["revision"] = dep.version
end

parsed_manifest["constraint"] << details
end

def go_dir
# Work in a directory called "$HOME/go/src/dependabot-tmp".
# TODO: This should pick up what the user's actual GOPATH is.
go_dir = File.join(Dir.home, "go", "src", "dependabot-tmp")
FileUtils.mkdir_p(go_dir)
go_dir
end

def dummy_app_content
base = "package main\n\n"\
"import \"fmt\"\n\n"

dependencies_to_import.each { |nm| base += "import \"#{nm}\"\n\n" }

base + "func main() {\n fmt.Printf(\"hello, world\\n\")\n}"
end

def dependencies_to_import
# There's no way to tell whether dependencies that appear in the
# lockfile are there because they're imported themselves or because
# they're sub-dependencies of something else. v0.5.0 will fix that
# problem, but for now we just have to import everything.
#
# NOTE: This means the `inputs-digest` we generate will be wrong.
# That's a pity, but we'd have to iterate through too many
# possibilities to get it right. Again, this is fixed in v0.5.0.
return [] unless lockfile
TomlRB.parse(lockfile.content).fetch("projects").map do |detail|
detail["name"]
end
end

def parsed_file(file)
@parsed_file ||= {}
@parsed_file[file.name] ||= TomlRB.parse(file.content)
end

def manifest
@manifest ||= dependency_files.find { |f| f.name == "Gopkg.toml" }
end

def lockfile
@lockfile ||= dependency_files.find { |f| f.name == "Gopkg.lock" }
end
end
end
end
end
end
1 change: 0 additions & 1 deletion lib/dependabot/file_updaters/go/dep/manifest_updater.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# frozen_string_literal: true

require "parser/current"
require "dependabot/file_updaters/go/dep"

module Dependabot
Expand Down
3 changes: 1 addition & 2 deletions lib/dependabot/update_checkers/go/dep.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# frozen_string_literal: true

require "toml-rb"
require "dependabot/update_checkers/base"

module Dependabot
Expand Down Expand Up @@ -211,8 +212,6 @@ def git_branch_or_ref_in_release?(release)
def parsed_file(file)
@parsed_file ||= {}
@parsed_file[file.name] ||= TomlRB.parse(file.content)
rescue TomlRB::ParseError
raise Dependabot::DependencyFileNotParseable, file.path
end

def manifest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,6 @@ def git_commit_checker
def parsed_file(file)
@parsed_file ||= {}
@parsed_file[file.name] ||= TomlRB.parse(file.content)
rescue TomlRB::ParseError
raise Dependabot::DependencyFileNotParseable, file.path
end

def version_class
Expand Down
22 changes: 22 additions & 0 deletions spec/fixtures/go/gopkg_locks/custom_source.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.


[[projects]]
name = "github.com/bsm/ratelimit"
packages = ["."]
revision = "4a38dca83ebadb017e83301642fab61b1c17edb7"
version = "v2.0.0"

[[projects]]
branch = "master"
name = "github.com/some/thing"
packages = ["."]
revision = "0b96aaa707760d6ab28d9b9d1913ff5993328bae"
source = "https://github.com/dgrijalva/jwt-go"

[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "677a622bfbc921ebb12df377c21012ff65974214b5d13a8fe77b2156c8d2f832"
solver-name = "gps-cdcl"
solver-version = 1
14 changes: 14 additions & 0 deletions spec/fixtures/go/gopkg_tomls/custom_source.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
required = [
"github.com/some/thing",
"github.com/bsm/ratelimit"
]

[[constraint]]
name = "github.com/some/thing"
branch = "master"
source = "https://github.com/dgrijalva/jwt-go"

[[constraint]]
name = "github.com/bsm/ratelimit"
version = "2.0.0"

0 comments on commit 4eb1515

Please sign in to comment.