Skip to content

Commit

Permalink
Merge pull request #404 from github/rusty
Browse files Browse the repository at this point in the history
Add support for rust via cargo
  • Loading branch information
jonabc authored Sep 19, 2021
2 parents 4a26ffe + 1fd1237 commit 9f75185
Show file tree
Hide file tree
Showing 11 changed files with 291 additions and 0 deletions.
36 changes: 36 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,42 @@ jobs:
- name: Run tests
run: script/test cabal

cargo:
runs-on: ubuntu-latest
needs: core
steps:
- uses: actions/checkout@v2
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.6
- name: Setup Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- run: bundle lock
- name: cache cargo dependencies
uses: actions/cache@v2
with:
path: |
~/.cargo/registry
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}
restore-keys: |
${{ runner.os }}-cargo-
- uses: actions/cache@v2
name: cache gem dependencies
with:
path: vendor/gems
key: ${{ runner.os }}-gem-2.6-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-gem-2.6-
- name: Bootstrap
run: script/bootstrap
- name: Set up fixtures
run: script/source-setup/cargo
- name: Run tests
run: script/test cargo

composer:
runs-on: ubuntu-latest
needs: core
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ test/fixtures/yarn/*

test/fixtures/nuget/obj/*

test/fixtures/cargo/*
!test/fixtures/cargo/Cargo.*
!test/fixtures/cargo/src

vendor/licenses
.licenses
*.gem
Expand Down
19 changes: 19 additions & 0 deletions docs/sources/cargo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Cargo

The cargo source will detect dependencies when `Cargo.toml` is found at an apps `source_path`. The source uses the `cargo metadata` CLI and reports on all dependencies that are listed in the output in `resolve.nodes`, excluding packages that are listed in `workspace_members`.

## Metadata CLI options

Licensed by default runs `cargo metadata --format-version=1`. You can specify additional CLI options by specifying them in your licensed configuration file under `cargo.metadata_options`. The configuration can be set as a string, or as an array of strings for multiple options.

```yml
cargo:
metadata_options: '--all-features'
```
```yml
cargo:
metadata_options:
- '--all-features'
- '--filter-platform x86_64-pc-windows-msvc'
```
1 change: 1 addition & 0 deletions lib/licensed/sources.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module Sources
require "licensed/sources/bower"
require "licensed/sources/bundler"
require "licensed/sources/cabal"
require "licensed/sources/cargo"
require "licensed/sources/composer"
require "licensed/sources/dep"
require "licensed/sources/git_submodule"
Expand Down
70 changes: 70 additions & 0 deletions lib/licensed/sources/cargo.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# frozen_string_literal: true

require "json"

module Licensed
module Sources
class Cargo < Source
# Source is enabled when the cargo tool and Cargo.toml manifest file are available
def enabled?
return false unless Licensed::Shell.tool_available?("cargo")
config.pwd.join("Cargo.toml").exist?
end

def enumerate_dependencies
packages.map do |package|
Dependency.new(
name: "#{package["name"]}-#{package["version"]}",
version: package["version"],
path: File.dirname(package["manifest_path"]),
metadata: {
"name" => package["name"],
"type" => Cargo.type,
"summary" => package["description"],
"homepage" => package["homepage"]
}
)
end
end

# Returns the package data for all dependencies used to build the current package
def packages
cargo_metadata_resolved_node_ids.map { |id| cargo_metadata_packages[id] }
end

# Returns the ids of all resolved nodes used to build the current package
def cargo_metadata_resolved_node_ids
cargo_metadata.dig("resolve", "nodes")
.map { |node| node["id"] }
.reject { |id| cargo_metadata_workspace_members.include?(id) }

end

# Returns a hash of id => package pairs sourced from the "packages" cargo metadata property
def cargo_metadata_packages
@cargo_metadata_packages ||= cargo_metadata["packages"].each_with_object({}) do |package, hsh|
hsh[package["id"]] = package
end
end

# Returns a set of the ids of packages in the current workspace
def cargo_metadata_workspace_members
@cargo_metadata_workspace_members ||= Set.new(Array(cargo_metadata["workspace_members"]))
end

# Returns parsed JSON metadata returned from the cargo CLI
def cargo_metadata
@cargo_metadata ||= JSON.parse(cargo_metadata_command)
rescue JSON::ParserError => e
message = "Licensed was unable to parse the output from 'cargo metadata'. JSON Error: #{e.message}"
raise Licensed::Sources::Source::Error, message
end

# Runs a command to get cargo metadata for the current package
def cargo_metadata_command
options = Array(config.dig("cargo", "metadata_options")).flat_map(&:split)
Licensed::Shell.execute("cargo", "metadata", "--format-version=1", *options)
end
end
end
end
19 changes: 19 additions & 0 deletions script/source-setup/cargo
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/bash
set -e

if [ -z "$(which cargo)" ]; then
echo "A local cargo installation is required for rustc development." >&2
exit 127
fi

cargo --version

# setup test fixtures
BASE_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd $BASE_PATH/test/fixtures/cargo

if [ "$1" == "-f" ]; then
find . -not -regex "\.*" -and -not -path "*app*" -print0 | xargs -0 rm -rf
fi

cargo build
25 changes: 25 additions & 0 deletions test/fixtures/cargo/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions test/fixtures/cargo/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "app"
version = "0.1.0"
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
time = "~0.3.0"
3 changes: 3 additions & 0 deletions test/fixtures/cargo/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}
6 changes: 6 additions & 0 deletions test/fixtures/command/cargo.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
expected_dependency: time-0.3.2
expected_dependency_name: time
source_path: test/fixtures/cargo
cache_path: test/fixtures/cargo/.licenses
sources:
cargo: true
99 changes: 99 additions & 0 deletions test/sources/cargo_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# frozen_string_literal: true
require "test_helper"
require "tmpdir"

if Licensed::Shell.tool_available?("cargo")
describe Licensed::Sources::Cargo do
let(:fixtures) { File.expand_path("../../fixtures/cargo", __FILE__) }
let(:config) { Licensed::AppConfiguration.new({ "source_path" => Dir.pwd }) }
let(:source) { Licensed::Sources::Cargo.new(config) }

describe "enabled?" do
it "is false if Cargo.toml does not exist" do
Dir.mktmpdir do |dir|
Dir.chdir(dir) do
refute source.enabled?
end
end
end

it "is true if Cargo.toml exists" do
Dir.chdir(fixtures) do
assert source.enabled?
end
end
end

describe "dependencies" do
it "does not include the current application" do
Dir.chdir fixtures do
refute source.dependencies.detect { |d| d.name == "app-0.1.0" }
end
end

it "includes declared dependencies" do
Dir.chdir fixtures do
dep = source.dependencies.detect { |d| d.name == "time-0.3.2" }
assert dep
assert_equal "cargo", dep.record["type"]
assert dep.record["homepage"]
assert dep.record["summary"]
assert dep.path
end
end

it "includes transitive dependencies" do
Dir.chdir fixtures do
dep = source.dependencies.detect { |d| d.name == "libc-0.2.102" }
assert dep
assert_equal "cargo", dep.record["type"]
assert dep.record["homepage"]
assert dep.record["summary"]
assert dep.path
end
end

it "does not include dev dependencies" do
Dir.chdir fixtures do
refute source.dependencies.detect { |dep| dep.name == "criterion" }
end
end

it "does not include not-installed optional dependencies" do
Dir.chdir fixtures do
refute source.dependencies.detect { |dep| dep.name == "time-macros" }
end
end
end

describe "cargo_metadata" do
it "raises Licensed::Sources::Source::Error if the cargo JSON metadata can't be parsed" do
Licensed::Shell.stub(:execute, "") do
Dir.chdir fixtures do
assert_raises Licensed::Sources::Source::Error do
source.cargo_metadata
end
end
end
end
end

describe "cargo_metadata_command" do
it "applies a configured string metadata cli option" do
Dir.chdir fixtures do
config["cargo"] = { "metadata_options" => "--all-features" }
Licensed::Shell.expects(:execute).with("cargo", "metadata", "--format-version=1", "--all-features")
source.cargo_metadata_command
end
end

it "applies a configured array of string metadata cli options" do
Dir.chdir fixtures do
config["cargo"] = { "metadata_options" => ["--all-features", "--filter-platform x86_64-pc-windows-msvc"] }
Licensed::Shell.expects(:execute).with("cargo", "metadata", "--format-version=1", "--all-features", "--filter-platform", "x86_64-pc-windows-msvc")
source.cargo_metadata_command
end
end
end
end
end

0 comments on commit 9f75185

Please sign in to comment.