diff --git a/Dockerfile b/Dockerfile index 41fc6776e..a84956575 100644 --- a/Dockerfile +++ b/Dockerfile @@ -116,6 +116,9 @@ RUN apt-get install -y python-dev && \ pip install --ignore-installed six --ignore-installed colorama --ignore-installed requests --ignore-installed chardet --ignore-installed urllib3 --upgrade setuptools && \ pip install conan +# install Cargo +RUN curl -sSf https://static.rust-lang.org/rustup.sh | sh -s -- --disable-sudo + # install license_finder COPY . /LicenseFinder RUN bash -lc "cd /LicenseFinder && bundle install -j4 && rake install" diff --git a/README.md b/README.md index 37812bd35..9e32eb781 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ report. * JavaScript (via `yarn`) * C++/C (via `conan`) * Scala (via `sbt`) +* Rust (via `cargo`) ## Installation @@ -174,6 +175,7 @@ languages, as long as that language has a package definition in the project dire * `yarn.lock` file (for `yarn`) * `conanfile.txt` file (for `conan`) * `build.sbt` file (for `sbt`) +* `Cargo.lock` file (for `cargo`) ### Continuous Integration diff --git a/lib/license_finder/package.rb b/lib/license_finder/package.rb index c72cce32d..859207ba5 100644 --- a/lib/license_finder/package.rb +++ b/lib/license_finder/package.rb @@ -184,3 +184,4 @@ def log_activation(activation) require 'license_finder/packages/conan_package' require 'license_finder/packages/yarn_package' require 'license_finder/packages/sbt_package' +require 'license_finder/packages/cargo_package' diff --git a/lib/license_finder/package_manager.rb b/lib/license_finder/package_manager.rb index 53988abfb..ccdf9c8ca 100644 --- a/lib/license_finder/package_manager.rb +++ b/lib/license_finder/package_manager.rb @@ -144,5 +144,6 @@ def log_to_file(contents) require 'license_finder/package_managers/dep' require 'license_finder/package_managers/conan' require 'license_finder/package_managers/sbt' +require 'license_finder/package_managers/cargo' require 'license_finder/package' diff --git a/lib/license_finder/package_managers/cargo.rb b/lib/license_finder/package_managers/cargo.rb new file mode 100644 index 000000000..8c6e7b683 --- /dev/null +++ b/lib/license_finder/package_managers/cargo.rb @@ -0,0 +1,34 @@ +require 'json' + +module LicenseFinder + class Cargo < PackageManager + def current_packages + cargo_output.map do |package| + CargoPackage.new(package, logger: logger) + end + end + + def self.package_management_command + 'cargo' + end + + def self.prepare_command + 'cargo fetch' + end + + def possible_package_paths + [project_path.join('Cargo.lock'), project_path.join('Cargo.toml')] + end + + private + + def cargo_output + command = "#{Cargo.package_management_command} metadata --format-version=1" + + stdout, stderr, status = Dir.chdir(project_path) { Cmd.run(command) } + raise "Command '#{command}' failed to execute: #{stderr}" unless status.success? + JSON(stdout) + .fetch('packages', []) + end + end +end diff --git a/lib/license_finder/packages/cargo_package.rb b/lib/license_finder/packages/cargo_package.rb new file mode 100644 index 000000000..a23971092 --- /dev/null +++ b/lib/license_finder/packages/cargo_package.rb @@ -0,0 +1,22 @@ +module LicenseFinder + class CargoPackage < Package + def initialize(crate, options = {}) + crate = crate.reject { |_, v| v.nil? || v == '' } + children = crate.fetch('dependencies', []).map { |p| p['name'] } + licenses = crate.fetch('license', '').split('/') + super( + crate['name'], + crate['version'], + options.merge( + summary: crate.fetch('description', '').strip, + spec_licenses: licenses.compact, + children: children + ) + ) + end + + def package_manager + 'Cargo' + end + end +end diff --git a/lib/license_finder/scanner.rb b/lib/license_finder/scanner.rb index 9136bf3f9..ad083f47b 100644 --- a/lib/license_finder/scanner.rb +++ b/lib/license_finder/scanner.rb @@ -1,7 +1,7 @@ module LicenseFinder class Scanner PACKAGE_MANAGERS = [GoDep, GoWorkspace, Go15VendorExperiment, Glide, Gvt, Govendor, Dep, Bundler, NPM, Pip, - Yarn, Bower, Maven, Gradle, CocoaPods, Rebar, Nuget, Carthage, Mix, Conan, Sbt].freeze + Yarn, Bower, Maven, Gradle, CocoaPods, Rebar, Nuget, Carthage, Mix, Conan, Sbt, Cargo].freeze def initialize(config = { project_path: Pathname.new('') }) @config = config diff --git a/spec/fixtures/all_pms/Cargo.lock b/spec/fixtures/all_pms/Cargo.lock new file mode 100644 index 000000000..e69de29bb diff --git a/spec/fixtures/all_pms/Cargo.toml b/spec/fixtures/all_pms/Cargo.toml new file mode 100644 index 000000000..e69de29bb diff --git a/spec/fixtures/config/cargo.json b/spec/fixtures/config/cargo.json new file mode 100644 index 000000000..64afd54f8 --- /dev/null +++ b/spec/fixtures/config/cargo.json @@ -0,0 +1,206 @@ +{ + "packages": [ + { + "name": "license-finder", + "version": "0.1.0", + "id": "license-finder 0.1.0 (path+file:///home/test/license-finder)", + "license": null, + "license_file": null, + "description": null, + "source": null, + "dependencies": [ + { + "name": "log", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "req": "*", + "kind": null, + "optional": false, + "uses_default_features": true, + "features": [], + "target": null + }, + { + "name": "simple_logger", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "req": "*", + "kind": null, + "optional": false, + "uses_default_features": true, + "features": [], + "target": null + } + ], + "targets": [ + { + "kind": [ + "bin" + ], + "crate_types": [ + "bin" + ], + "name": "deps-finder-test", + "src_path": "/home/test/code/centauri/deps-finder-test/src/main.rs" + } + ], + "features": {}, + "manifest_path": "/home/test/code/centauri/deps-finder-test/Cargo.toml" + }, + { + "name": "log", + "version": "0.4.1", + "id": "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "license": "MIT/Apache-2.0", + "license_file": null, + "description": "A lightweight logging facade for Rust\n", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "dependencies": [], + "targets": [ + { + "kind": [ + "lib" + ], + "crate_types": [ + "lib" + ], + "name": "log", + "src_path": "/home/test/.cargo/registry/src/github.com-1ecc6299db9ec823/log-0.4.1/src/lib.rs" + }, + { + "kind": [ + "test" + ], + "crate_types": [ + "bin" + ], + "name": "filters", + "src_path": "/home/test/.cargo/registry/src/github.com-1ecc6299db9ec823/log-0.4.1/tests/filters.rs" + } + ], + "features": {}, + "manifest_path": "/home/test/.cargo/registry/src/github.com-1ecc6299db9ec823/log-0.4.1/Cargo.toml" + }, + { + "name": "simple_logger", + "version": "0.5.0", + "id": "simple_logger 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "license": "MIT", + "license_file": null, + "description": "A logger that prints all messages with a readable output format", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "dependencies": [ + { + "name": "log", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "req": "^0.4.1", + "kind": null, + "optional": false, + "uses_default_features": true, + "features": [ + "std" + ], + "target": null + } + ], + "targets": [ + { + "kind": [ + "lib" + ], + "crate_types": [ + "lib" + ], + "name": "simple_logger", + "src_path": "/home/test/.cargo/registry/src/github.com-1ecc6299db9ec823/simple_logger-0.5.0/src/lib.rs" + }, + { + "kind": [ + "example" + ], + "crate_types": [ + "bin" + ], + "name": "init", + "src_path": "/home/test/.cargo/registry/src/github.com-1ecc6299db9ec823/simple_logger-0.5.0/examples/init.rs" + }, + { + "kind": [ + "example" + ], + "crate_types": [ + "bin" + ], + "name": "init_with_level", + "src_path": "/home/test/.cargo/registry/src/github.com-1ecc6299db9ec823/simple_logger-0.5.0/examples/init_with_level.rs" + } + ], + "features": {}, + "manifest_path": "/home/test/.cargo/registry/src/github.com-1ecc6299db9ec823/simple_logger-0.5.0/Cargo.toml" + } + ], + "workspace_members": [ + "thing 0.1.0 (path+file:///path/to/thing)" + ], + "resolve": { + "nodes": [ + { + "id": "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "dependencies": [ + "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" + ] + }, + { + "id": "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "dependencies": [] + }, + { + "id": "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "dependencies": [] + }, + { + "id": "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "dependencies": [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" + ] + }, + { + "id": "time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", + "dependencies": [ + "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" + ] + }, + { + "id": "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", + "dependencies": [] + }, + { + "id": "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", + "dependencies": [] + }, + { + "id": "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "dependencies": [] + }, + { + "id": "simple_logger 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "dependencies": [ + "time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" + ] + }, + { + "id": "deps-finder-test 0.1.0 (path+file:///path/to/thing)", + "dependencies": [ + "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "simple_logger 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" + ] + } + ], + "root": "deps-finder-test 0.1.0 (path+file:///path/to/thing)" + }, + "target_directory": "/path/to/thing/target", + "version": 1, + "workspace_root": "/path/to/thing" +} \ No newline at end of file diff --git a/spec/lib/license_finder/package_managers/cargo_package_spec.rb b/spec/lib/license_finder/package_managers/cargo_package_spec.rb new file mode 100644 index 000000000..a6c8d9ac7 --- /dev/null +++ b/spec/lib/license_finder/package_managers/cargo_package_spec.rb @@ -0,0 +1,66 @@ +require 'spec_helper' + +module LicenseFinder + describe CargoPackage do + subject do + described_class.new( + 'name' => 'time', + 'version' => '0.1.39', + 'id' => 'time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)', + 'license' => 'MIT/Apache-2.0', + 'license_file' => nil, + 'description' => "Utilities for working with time-related functions in Rust.\n", + 'source' => 'registry+https://github.com/rust-lang/crates.io-index', + 'dependencies' => [ + { + 'name' => 'libc', + 'source' => 'registry+https://github.com/rust-lang/crates.io-index', + 'req' => '^0.2.1', + 'kind' => nil, + 'optional' => false, + 'uses_default_features' => true, + 'features' => [], + 'target' => nil + } + ], + 'targets' => [ + { + 'kind' => [ + 'lib' + ], + 'crate_types' => [ + 'lib' + ], + 'name' => 'time', + 'src_path' => '/home/test/.cargo/registry/src/github.com-1ecc6299db9ec823/time-0.1.39/src/lib.rs' + } + ], + 'features' => {}, + 'manifest_path' => '/home/test/.cargo/registry/src/github.com-1ecc6299db9ec823/time-0.1.39/Cargo.toml' + ) + end + + its(:name) { should == 'time' } + its(:version) { should == '0.1.39' } + its(:summary) { should == 'Utilities for working with time-related functions in Rust.' } + its(:homepage) { should eq '' } + its(:groups) { should == [] } + its(:children) { should == ['libc'] } + its(:package_manager) { should eq 'Cargo' } + + describe '#license_names_from_spec' do + let(:package1) { { 'license' => 'MIT' } } + let(:package2) { { 'license' => 'MIT/Apache-2.0' } } + let(:package3) { { 'license' => 'PSF' } } + let(:package4) { { 'licenses' => ['MIT'] } } + + it 'finds the license for all license structures' do + package = CargoPackage.new(package1) + expect(package.license_names_from_spec).to eq ['MIT'] + + package = CargoPackage.new(package2) + expect(package.license_names_from_spec).to eq ['MIT', 'Apache-2.0'] + end + end + end +end diff --git a/spec/lib/license_finder/package_managers/cargo_spec.rb b/spec/lib/license_finder/package_managers/cargo_spec.rb new file mode 100644 index 000000000..bf4a17cf5 --- /dev/null +++ b/spec/lib/license_finder/package_managers/cargo_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +module LicenseFinder + describe Cargo do + subject { Cargo.new(project_path: Pathname('/fake/path')) } + + it_behaves_like 'a PackageManager' + + describe '.current_packages' do + it 'lists all the current packages' do + json = fixture_from('cargo.json') + + allow(Dir).to receive(:chdir).with(Pathname('/fake/path')) { |&block| block.call } + allow(SharedHelpers::Cmd).to receive(:run) + .with('cargo metadata --format-version=1') + .and_return([json, '', cmd_success]) + + expect(subject.current_packages.map(&:name)).to eq %w[license-finder log simple_logger] + end + end + + it 'should return the correct prepare command' do + expect(Cargo.prepare_command).to eq('cargo fetch') + end + end +end